From a177452778f6305e1993d811b3d73ef2f3b84669 Mon Sep 17 00:00:00 2001 From: Sven Sauleau Date: Tue, 7 Feb 2023 14:43:27 +0000 Subject: [PATCH] regenerate the home timeline after status deletion --- backend/test/mastodon/statuses.spec.ts | 61 ++++++++++++++++++++++++-- functions/api/v1/statuses/[id].ts | 18 +++++++- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/backend/test/mastodon/statuses.spec.ts b/backend/test/mastodon/statuses.spec.ts index d626910..639f694 100644 --- a/backend/test/mastodon/statuses.spec.ts +++ b/backend/test/mastodon/statuses.spec.ts @@ -815,7 +815,7 @@ describe('Mastodon APIs', () => { const queue = makeQueue() const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') const mastodonId = 'abcd' - const res = await statuses_id.handleRequestDelete(db, mastodonId, actor, domain, userKEK, queue) + const res = await statuses_id.handleRequestDelete(db, mastodonId, actor, domain, userKEK, queue, cache) assert.equal(res.status, 404) }) @@ -826,7 +826,15 @@ describe('Mastodon APIs', () => { const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com') const note = await createPublicNote(domain, db, 'note from actor2', actor2) - const res = await statuses_id.handleRequestDelete(db, note[mastodonIdSymbol]!, actor, domain, userKEK, queue) + const res = await statuses_id.handleRequestDelete( + db, + note[mastodonIdSymbol]!, + actor, + domain, + userKEK, + queue, + cache + ) assert.equal(res.status, 404) }) @@ -837,7 +845,15 @@ describe('Mastodon APIs', () => { const note = await createPublicNote(domain, db, 'note from actor', actor) await addObjectInOutbox(db, actor, note) - const res = await statuses_id.handleRequestDelete(db, note[mastodonIdSymbol]!, actor, domain, userKEK, queue) + const res = await statuses_id.handleRequestDelete( + db, + note[mastodonIdSymbol]!, + actor, + domain, + userKEK, + queue, + cache + ) assert.equal(res.status, 200) { @@ -850,6 +866,35 @@ describe('Mastodon APIs', () => { } }) + test('delete status regenerates the timeline', async () => { + const db = await makeDB() + const queue = makeQueue() + const cache = makeCache() + const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') + const note = await createPublicNote(domain, db, 'note from actor', actor) + await addObjectInOutbox(db, actor, note) + + // Poison the timeline + await cache.put(actor.id.toString() + '/timeline/home', 'funny value') + + const res = await statuses_id.handleRequestDelete( + db, + note[mastodonIdSymbol]!, + actor, + domain, + userKEK, + queue, + cache + ) + assert.equal(res.status, 200) + + // ensure that timeline has been regenerated after the deletion + // and that timeline is empty + const timeline = await cache.get>(actor.id.toString() + '/timeline/home') + assert(timeline) + assert.equal(timeline!.length, 0) + }) + test('delete status sends to followers', async () => { const db = await makeDB() const queue = makeQueue() @@ -863,7 +908,15 @@ describe('Mastodon APIs', () => { await addFollowing(db, actor3, actor, 'not needed') await acceptFollowing(db, actor3, actor) - const res = await statuses_id.handleRequestDelete(db, note[mastodonIdSymbol]!, actor, domain, userKEK, queue) + const res = await statuses_id.handleRequestDelete( + db, + note[mastodonIdSymbol]!, + actor, + domain, + userKEK, + queue, + cache + ) assert.equal(res.status, 200) assert.equal(queue.messages.length, 2) diff --git a/functions/api/v1/statuses/[id].ts b/functions/api/v1/statuses/[id].ts index 870be17..4aaa541 100644 --- a/functions/api/v1/statuses/[id].ts +++ b/functions/api/v1/statuses/[id].ts @@ -1,5 +1,6 @@ // https://docs.joinmastodon.org/methods/statuses/#get +import type { Cache } from 'wildebeest/backend/src/cache' import { type Note } from 'wildebeest/backend/src/activitypub/objects/note' import * as activities from 'wildebeest/backend/src/activitypub/activities/delete' import { cors } from 'wildebeest/backend/src/utils/cors' @@ -13,6 +14,8 @@ import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/object import { urlToHandle } from 'wildebeest/backend/src/utils/handle' import { deliverFollowers } from 'wildebeest/backend/src/activitypub/deliver' import type { Queue, DeliverMessageBody } from 'wildebeest/backend/src/types/queue' +import * as timeline from 'wildebeest/backend/src/mastodon/timeline' +import { cacheFromEnv } from 'wildebeest/backend/src/cache' export const onRequestGet: PagesFunction = async ({ params, env, request }) => { const domain = new URL(request.url).hostname @@ -21,7 +24,15 @@ export const onRequestGet: PagesFunction = async ({ param export const onRequestDelete: PagesFunction = async ({ params, env, request, data }) => { const domain = new URL(request.url).hostname - return handleRequestDelete(env.DATABASE, params.id as UUID, data.connectedActor, domain, env.userKEK, env.QUEUE) + return handleRequestDelete( + env.DATABASE, + params.id as UUID, + data.connectedActor, + domain, + env.userKEK, + env.QUEUE, + cacheFromEnv(env) + ) } export async function handleRequestGet(db: D1Database, id: UUID, domain: string): Promise { @@ -68,7 +79,8 @@ export async function handleRequestDelete( connectedActor: Person, domain: string, userKEK: string, - queue: Queue + queue: Queue, + cache: Cache ): Promise { const obj = (await getObjectByMastodonId(db, id)) as Note if (obj === null) { @@ -89,6 +101,8 @@ export async function handleRequestDelete( const activity = activities.create(domain, connectedActor, obj) await deliverFollowers(db, userKEK, connectedActor, activity, queue) + await timeline.pregenerateTimelines(domain, db, cache, connectedActor) + const headers = { ...cors(), 'content-type': 'application/json; charset=utf-8',