From bc6873422f7bd50c3c4bb7ff1f17a941398b41eb Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Thu, 14 Sep 2023 17:42:52 -1000 Subject: [PATCH] More tests, make Delete objects work --- server/api/[username]/inbox/index.ts | 78 +++++++++++++++++++++------- tests/inbox.test.ts | 9 +++- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/server/api/[username]/inbox/index.ts b/server/api/[username]/inbox/index.ts index c714be9b..907b3173 100644 --- a/server/api/[username]/inbox/index.ts +++ b/server/api/[username]/inbox/index.ts @@ -13,6 +13,7 @@ import { APUpdate, } from "activitypub-types"; import { MatchedRoute } from "bun"; +import { appendFile } from "fs/promises"; import { RawActivity } from "~database/entities/RawActivity"; import { RawObject } from "~database/entities/RawObject"; @@ -43,24 +44,49 @@ export default async ( // TODO: Add authentication // Check is Activity already exists - const exists = await RawActivity.findOneBy({ - data: { + const exists = await RawActivity.createQueryBuilder("activity") + .where("activity.data->>'id' = :id", { id: body.id, - }, - }); + }) + .getOne(); if (exists) return errorResponse("Activity already exists", 409); // Check if object already exists - const objectExists = await RawObject.findOneBy({ - data: { + const objectExists = await RawObject.createQueryBuilder("object") + .where("object.data->>'id' = :id", { id: (body.object as APObject).id, - }, - }); + }) + .getOne(); if (objectExists) return errorResponse("Object already exists", 409); + // Check if object body contains any filtered terms + const filter_result = await Promise.all( + config.filters.note_filters.map(async filter => { + if ( + (body.object as APObject).type === "Note" && + (body.object as APObject).content?.match(filter) + ) { + // Log filter + + if (config.logging.log_filters) + await appendFile( + process.cwd() + "/logs/filters.log", + `${new Date().toISOString()} Filtered note content: "${( + body.object as APObject + ).content?.replaceAll("\n", " ")}" (ID: ${ + (body.object as APObject).id + }) based on rule: ${filter}\n` + ); + return true; + } + }) + ); + + if (filter_result.includes(true)) return jsonResponse({}); + const activity = new RawActivity(); const object = new RawObject(); @@ -109,19 +135,23 @@ export default async ( // Delete the object from database // TODO: Add authentication - const object = await RawObject.findOneBy({ - data: { - id: (body.object as RawObject).id, - }, - }); + const object = await RawObject.createQueryBuilder("object") + .where("object.data->>'id' = :id", { + id: (body.object as APObject).id, + }) + .getOne(); if (!object) return errorResponse("Object not found", 404); - const activities = await RawActivity.findBy({ - objects: { - id: (body.object as RawObject).id, - }, - }); + const activities = await RawActivity.createQueryBuilder("activity") + // Objects is a many-to-many relationship + .leftJoinAndSelect("activity.objects", "objects") + .where("objects.data @> :data", { + data: JSON.stringify({ + id: object.id, + }), + }) + .getMany(); if (config.activitypub.use_tombstones) { object.data = { @@ -146,6 +176,18 @@ export default async ( await object.remove(); } + + // Store the Delete event in the database + const activity = new RawActivity(); + activity.data = { + ...body, + object: undefined, + }; + activity.objects = config.activitypub.use_tombstones + ? [object] + : []; + + await activity.save(); break; } case "Accept" as APAccept: { diff --git a/tests/inbox.test.ts b/tests/inbox.test.ts index b211574e..f90f96bf 100644 --- a/tests/inbox.test.ts +++ b/tests/inbox.test.ts @@ -171,7 +171,7 @@ describe("POST /@test/inbox", () => { "@context": "https://www.w3.org/ns/activitystreams", id: "https://example.com/notes/1", type: "Note", - content: "This note has been deleted!", + content: "This note has been edited!", summary: null, inReplyTo: null, published: "2021-01-01T00:00:00.000Z", @@ -204,7 +204,12 @@ describe("POST /@test/inbox", () => { published: "2021-01-03T00:00:00.000Z", }); - expect(activity?.objects).toHaveLength(0); + // Can be 0 or 1 length depending on whether config.activitypub.use_tombstone is true or false + if (config.activitypub.use_tombstones) { + expect(activity?.objects).toHaveLength(1); + } else { + expect(activity?.objects).toHaveLength(0); + } }); });