diff --git a/database/entities/Federation.ts b/database/entities/Federation.ts index c457ad25..807c114c 100644 --- a/database/entities/Federation.ts +++ b/database/entities/Federation.ts @@ -67,3 +67,18 @@ export const objectToInboxRequest = async ( return signed; }; + +export const undoFederationRequest = ( + undoer: User, + uri: string, +): typeof EntityValidator.$Undo => { + const id = crypto.randomUUID(); + return { + type: "Undo", + id, + author: undoer.getUri(), + created_at: new Date().toISOString(), + object: uri, + uri: new URL(`/undos/${id}`, config.http.base_url).toString(), + }; +}; diff --git a/packages/database-interface/user.ts b/packages/database-interface/user.ts index dc69d62a..4de5d537 100644 --- a/packages/database-interface/user.ts +++ b/packages/database-interface/user.ts @@ -520,29 +520,33 @@ export class User { data.endpoints || data.isDiscoverable ) { - // Get followers - const followers = await User.manyFromSql( - and( - sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${this.id} AND "Relationships"."ownerId" = ${Users.id} AND "Relationships"."following" = true)`, - isNotNull(Users.instanceId), - ), - ); - - for (const follower of followers) { - const federationRequest = await objectToInboxRequest( - this.toLysand(), - this, - follower, - ); - - // FIXME: Add to new queue system when it's implemented - fetch(federationRequest); - } + await this.federateToFollowers(this.toLysand()); } return this; } + async federateToFollowers(object: typeof EntityValidator.$Entity) { + // Get followers + const followers = await User.manyFromSql( + and( + sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."subjectId" = ${this.id} AND "Relationships"."ownerId" = ${Users.id} AND "Relationships"."following" = true)`, + isNotNull(Users.instanceId), + ), + ); + + for (const follower of followers) { + const federationRequest = await objectToInboxRequest( + object, + this, + follower, + ); + + // FIXME: Add to new queue system when it's implemented + fetch(federationRequest); + } + } + toAPI(isOwnAccount = false): APIAccount { const user = this.getUser(); return { diff --git a/server/api/api/v1/statuses/:id/index.ts b/server/api/api/v1/statuses/:id/index.ts index f22d8ba7..9e6c0b3d 100644 --- a/server/api/api/v1/statuses/:id/index.ts +++ b/server/api/api/v1/statuses/:id/index.ts @@ -11,6 +11,7 @@ import { config } from "config-manager"; import type { Hono } from "hono"; import ISO6391 from "iso-639-1"; import { z } from "zod"; +import { undoFederationRequest } from "~/database/entities/Federation"; import { db } from "~/drizzle/db"; import { Note } from "~/packages/database-interface/note"; @@ -99,6 +100,10 @@ export default (app: Hono) => await foundStatus.delete(); + await user.federateToFollowers( + undoFederationRequest(user, foundStatus.getURI()), + ); + return jsonResponse(await foundStatus.toAPI(user), 200); } diff --git a/server/api/api/v1/statuses/:id/unreblog.ts b/server/api/api/v1/statuses/:id/unreblog.ts index b3b9feae..2ef78f60 100644 --- a/server/api/api/v1/statuses/:id/unreblog.ts +++ b/server/api/api/v1/statuses/:id/unreblog.ts @@ -4,6 +4,7 @@ import { zValidator } from "@hono/zod-validator"; import { and, eq } from "drizzle-orm"; import type { Hono } from "hono"; import { z } from "zod"; +import { undoFederationRequest } from "~/database/entities/Federation"; import { Notes } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; @@ -58,6 +59,10 @@ export default (app: Hono) => await existingReblog.delete(); + await user.federateToFollowers( + undoFederationRequest(user, existingReblog.getURI()), + ); + const newNote = await Note.fromId(id, user.id); if (!newNote) return errorResponse("Record not found", 404);