diff --git a/drizzle/0002_remote_users_table.sql b/drizzle/0002_remote_users_table.sql new file mode 100644 index 0000000..2a0dfd4 --- /dev/null +++ b/drizzle/0002_remote_users_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE `remoteUser` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `domain` text, + `cachedActivity` text, + `expiry` integer +); diff --git a/drizzle/0003_followers_table.sql b/drizzle/0003_followers_table.sql new file mode 100644 index 0000000..bdba7eb --- /dev/null +++ b/drizzle/0003_followers_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE `followers` ( + `id` integer PRIMARY KEY NOT NULL, + `followingCollection` text NOT NULL, + `userid` integer, + `remoteUserid` integer, + FOREIGN KEY (`userid`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`remoteUserid`) REFERENCES `remoteUser`(`id`) ON UPDATE no action ON DELETE no action +); diff --git a/drizzle/0004_follow_accept_activity_table.sql b/drizzle/0004_follow_accept_activity_table.sql new file mode 100644 index 0000000..49e4f28 --- /dev/null +++ b/drizzle/0004_follow_accept_activity_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE `followAcceptActivity` ( + `id` integer PRIMARY KEY NOT NULL, + `userid` integer NOT NULL, + `remoteUserid` integer NOT NULL, + `activity` text, + FOREIGN KEY (`userid`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`remoteUserid`) REFERENCES `remoteUser`(`id`) ON UPDATE no action ON DELETE no action +); diff --git a/drizzle/0005_remote_user_cached_actor_url_column.sql b/drizzle/0005_remote_user_cached_actor_url_column.sql new file mode 100644 index 0000000..be1f513 --- /dev/null +++ b/drizzle/0005_remote_user_cached_actor_url_column.sql @@ -0,0 +1 @@ +ALTER TABLE `remoteUser` ADD `actor` text; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..33ab062 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,157 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "26aaeabf-e6e2-4723-80ec-33520c4d49f1", + "prevId": "2ef072ba-1781-4656-80d5-2ae9c3e29638", + "tables": { + "pubPrivKeys": { + "name": "pubPrivKeys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "pubPrivKeys_userid_user_id_fk": { + "name": "pubPrivKeys_userid_user_id_fk", + "tableFrom": "pubPrivKeys", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "remoteUser": { + "name": "remoteUser", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cachedActivity": { + "name": "cachedActivity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expiry": { + "name": "expiry", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pwhash": { + "name": "pwhash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_name_unique": { + "name": "user_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..7ee03c4 --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,221 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "9abdf6af-70ff-42af-83a5-cfe7f3318620", + "prevId": "26aaeabf-e6e2-4723-80ec-33520c4d49f1", + "tables": { + "followers": { + "name": "followers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "followingCollection": { + "name": "followingCollection", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remoteUserid": { + "name": "remoteUserid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "followers_userid_user_id_fk": { + "name": "followers_userid_user_id_fk", + "tableFrom": "followers", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "followers_remoteUserid_remoteUser_id_fk": { + "name": "followers_remoteUserid_remoteUser_id_fk", + "tableFrom": "followers", + "tableTo": "remoteUser", + "columnsFrom": [ + "remoteUserid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "pubPrivKeys": { + "name": "pubPrivKeys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "pubPrivKeys_userid_user_id_fk": { + "name": "pubPrivKeys_userid_user_id_fk", + "tableFrom": "pubPrivKeys", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "remoteUser": { + "name": "remoteUser", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cachedActivity": { + "name": "cachedActivity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expiry": { + "name": "expiry", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pwhash": { + "name": "pwhash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_name_unique": { + "name": "user_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json new file mode 100644 index 0000000..9e9207b --- /dev/null +++ b/drizzle/meta/0004_snapshot.json @@ -0,0 +1,285 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "df1b4c75-8b8f-4e4b-b942-f98ee63fc784", + "prevId": "9abdf6af-70ff-42af-83a5-cfe7f3318620", + "tables": { + "followAcceptActivity": { + "name": "followAcceptActivity", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "remoteUserid": { + "name": "remoteUserid", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "followAcceptActivity_userid_user_id_fk": { + "name": "followAcceptActivity_userid_user_id_fk", + "tableFrom": "followAcceptActivity", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "followAcceptActivity_remoteUserid_remoteUser_id_fk": { + "name": "followAcceptActivity_remoteUserid_remoteUser_id_fk", + "tableFrom": "followAcceptActivity", + "tableTo": "remoteUser", + "columnsFrom": [ + "remoteUserid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "followers": { + "name": "followers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "followingCollection": { + "name": "followingCollection", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remoteUserid": { + "name": "remoteUserid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "followers_userid_user_id_fk": { + "name": "followers_userid_user_id_fk", + "tableFrom": "followers", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "followers_remoteUserid_remoteUser_id_fk": { + "name": "followers_remoteUserid_remoteUser_id_fk", + "tableFrom": "followers", + "tableTo": "remoteUser", + "columnsFrom": [ + "remoteUserid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "pubPrivKeys": { + "name": "pubPrivKeys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "pubPrivKeys_userid_user_id_fk": { + "name": "pubPrivKeys_userid_user_id_fk", + "tableFrom": "pubPrivKeys", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "remoteUser": { + "name": "remoteUser", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cachedActivity": { + "name": "cachedActivity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expiry": { + "name": "expiry", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pwhash": { + "name": "pwhash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_name_unique": { + "name": "user_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..d1c6fb5 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,292 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "071e7c99-3a77-42dd-b86f-148bf1b146db", + "prevId": "df1b4c75-8b8f-4e4b-b942-f98ee63fc784", + "tables": { + "followAcceptActivity": { + "name": "followAcceptActivity", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "remoteUserid": { + "name": "remoteUserid", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "followAcceptActivity_userid_user_id_fk": { + "name": "followAcceptActivity_userid_user_id_fk", + "tableFrom": "followAcceptActivity", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "followAcceptActivity_remoteUserid_remoteUser_id_fk": { + "name": "followAcceptActivity_remoteUserid_remoteUser_id_fk", + "tableFrom": "followAcceptActivity", + "tableTo": "remoteUser", + "columnsFrom": [ + "remoteUserid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "followers": { + "name": "followers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "followingCollection": { + "name": "followingCollection", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remoteUserid": { + "name": "remoteUserid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "followers_userid_user_id_fk": { + "name": "followers_userid_user_id_fk", + "tableFrom": "followers", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "followers_remoteUserid_remoteUser_id_fk": { + "name": "followers_remoteUserid_remoteUser_id_fk", + "tableFrom": "followers", + "tableTo": "remoteUser", + "columnsFrom": [ + "remoteUserid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "pubPrivKeys": { + "name": "pubPrivKeys", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userid": { + "name": "userid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "pubPrivKeys_userid_user_id_fk": { + "name": "pubPrivKeys_userid_user_id_fk", + "tableFrom": "pubPrivKeys", + "tableTo": "user", + "columnsFrom": [ + "userid" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "remoteUser": { + "name": "remoteUser", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cachedActivity": { + "name": "cachedActivity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expiry": { + "name": "expiry", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pwhash": { + "name": "pwhash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_name_unique": { + "name": "user_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 469b64b..a1e6f2f 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,34 @@ "when": 1722228088866, "tag": "0001_public_private_keys_table", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1722489767897, + "tag": "0002_remote_users_table", + "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1722493945807, + "tag": "0003_followers_table", + "breakpoints": true + }, + { + "idx": 4, + "version": "6", + "when": 1722495120997, + "tag": "0004_follow_accept_activity_table", + "breakpoints": true + }, + { + "idx": 5, + "version": "6", + "when": 1722495162448, + "tag": "0005_remote_user_cached_actor_url_column", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/api/activitypub/user/[user]/inbox/route.ts b/src/app/api/activitypub/user/[user]/inbox/route.ts new file mode 100644 index 0000000..e8fd750 --- /dev/null +++ b/src/app/api/activitypub/user/[user]/inbox/route.ts @@ -0,0 +1,71 @@ +import { NextRequest } from "next/server"; +import * as jsonld from 'jsonld'; +import db from "@/db/drizzle"; +import { followAcceptActivity, remoteUser, user } from "@/db/schema"; +import { eq } from "drizzle-orm"; + +export const dynamic = 'force-dynamic'; + +export async function POST(request: NextRequest) { + // TODO: HTTP Signature Validation (middleware?) + const originBody = await request.json(); + const jsonldBody = await jsonld.compact(originBody, ["https://www.w3.org/ns/activitystreams"]); + if ( jsonldBody.type === "Follow" ) { + for ( let toActor of jsonldBody.to ) { + let toActorUrl = new URL(toActor); + let matches = toActorUrl.pathname.match(/\/api\/activitypub\/user\/(.*)/); + // TODO: Check domain as well. Hopefully brainz gets installed on multiple instances, and this'll cause problems. + if ( matches && matches[1] ) { + const userRow = await db.select({id: user.id}).from(user).where(eq(user.name, matches[1])); + if ( userRow[0] ) { + // TODO: Implement user consent, moderation. + let response = { + "@context": ["https://www.w3.org/ns/activitystreams"], + "actor": toActor, + "object": {...jsonldBody}, + "to": [jsonldBody.actor], + "type": "Accept", + "id": "PLACEHOLDER" + } + + delete response.object["@context"]; + delete response.object.to; + + // First, check the validity of the cache. + const remoteUserRow = await db.select({id: remoteUser.id, cachedActivity: remoteUser.cachedActivity, cacheTimeout: remoteUser.cacheTimeout}).from(remoteUser).where(eq(remoteUser.cachedActorUrl, response.object.actor)); + let remoteUserId; + if ( remoteUserRow[0]?.cacheTimeout ) { + if (Date.now() < remoteUserRow[0].cacheTimeout ) { + remoteUserId = remoteUserRow[0].id; + } + } + + // TODO: What to do if that didn't provide a remoteUserId. + + if ( remoteUserId ) { + const placeholderJSON = JSON.stringify(response, null, 4); + await db.insert(followAcceptActivity).values({user: userRow[0].id, remoteUserid: remoteUserId, activity: placeholderJSON}); + const assignedId = await db.select({id: followAcceptActivity.id}).from(followAcceptActivity).where(eq(followAcceptActivity.activity, placeholderJSON)); + if ( assignedId[0]?.id ) { + response.id = new URL(`/api/activitypub/follow_accept/${assignedId[0].id}`, toActorUrl).toString(); + await db.update(followAcceptActivity).set({activity: JSON.stringify(response, null, 4)}).where(eq(followAcceptActivity.id, assignedId[0].id)); + } + } + + // TODO: Emit the activity. + return new Response("", { + status: 204 + }) + } + } + } + + return new Response("", { + status: 401 // Not authorized, seems appropriate here. + }) + } + + return new Response("", { + status: 501 // Not implemented. + }) +} \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts index c6b8e90..1b6fd37 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -13,4 +13,27 @@ export const pubPrivKeys = sqliteTable("pubPrivKeys", { user: int("userid").references(() => user.id), publicKey: text("publicKey"), privateKey: text("privateKey") +}) + +export const remoteUser = sqliteTable("remoteUser", { + id: int("id").primaryKey(), + name: text("name").notNull(), + domain: text("domain"), + cachedActorUrl: text("actor"), + cachedActivity: text("cachedActivity"), + cacheTimeout: int("expiry") +}) + +export const followers = sqliteTable("followers", { + id: int("id").primaryKey(), + followingCollection: text("followingCollection").notNull(), + userid: int("userid").references(() => user.id), + remoteUserid: int("remoteUserid").references(() => remoteUser.id) +}) + +export const followAcceptActivity = sqliteTable("followAcceptActivity", { + id: int("id").primaryKey(), + user: int("userid").references(() => user.id).notNull(), + remoteUserid: int("remoteUserid").references(() => remoteUser.id).notNull(), + activity: text("activity") }) \ No newline at end of file