kopia lustrzana https://github.com/cloudflare/wildebeest
MOW-126: basic local status deletion
This change deletes the Note from local object and Actor's outbox tables.pull/205/head
rodzic
3388675ad3
commit
f0a6b695a6
|
@ -2,10 +2,9 @@ import { strict as assert } from 'node:assert/strict'
|
|||
import { createReply } from 'wildebeest/backend/test/shared.utils'
|
||||
import { createStatus, getMentions } from 'wildebeest/backend/src/mastodon/status'
|
||||
import { createPublicNote, type Note } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
|
||||
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
|
||||
import * as statuses from 'wildebeest/functions/api/v1/statuses'
|
||||
import * as statuses_get from 'wildebeest/functions/api/v1/statuses/[id]'
|
||||
import * as statuses_id from 'wildebeest/functions/api/v1/statuses/[id]'
|
||||
import * as statuses_favourite from 'wildebeest/functions/api/v1/statuses/[id]/favourite'
|
||||
import * as statuses_reblog from 'wildebeest/functions/api/v1/statuses/[id]/reblog'
|
||||
import * as statuses_context from 'wildebeest/functions/api/v1/statuses/[id]/context'
|
||||
|
@ -17,7 +16,7 @@ import * as activities from 'wildebeest/backend/src/activitypub/activities'
|
|||
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||
import { MessageType } from 'wildebeest/backend/src/types/queue'
|
||||
import { MastodonStatus } from 'wildebeest/backend/src/types'
|
||||
import { mastodonIdSymbol } from 'wildebeest/backend/src/activitypub/objects'
|
||||
import { mastodonIdSymbol, getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
|
||||
|
||||
const userKEK = 'test_kek4'
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||
|
@ -537,7 +536,7 @@ describe('Mastodon APIs', () => {
|
|||
await insertLike(db, actor2, note)
|
||||
await insertLike(db, actor3, note)
|
||||
|
||||
const res = await statuses_get.handleRequest(db, note[mastodonIdSymbol]!, domain)
|
||||
const res = await statuses_id.handleRequestGet(db, note[mastodonIdSymbol]!, domain)
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<any>()
|
||||
|
@ -553,7 +552,7 @@ describe('Mastodon APIs', () => {
|
|||
const mediaAttachments = [await createImage(domain, db, actor, properties)]
|
||||
const note = await createPublicNote(domain, db, 'my first status', actor, mediaAttachments)
|
||||
|
||||
const res = await statuses_get.handleRequest(db, note[mastodonIdSymbol]!, domain)
|
||||
const res = await statuses_id.handleRequestGet(db, note[mastodonIdSymbol]!, domain)
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<any>()
|
||||
|
@ -592,7 +591,7 @@ describe('Mastodon APIs', () => {
|
|||
await insertReblog(db, actor2, note)
|
||||
await insertReblog(db, actor3, note)
|
||||
|
||||
const res = await statuses_get.handleRequest(db, note[mastodonIdSymbol]!, domain)
|
||||
const res = await statuses_id.handleRequestGet(db, note[mastodonIdSymbol]!, domain)
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
const data = await res.json<any>()
|
||||
|
@ -810,5 +809,41 @@ describe('Mastodon APIs', () => {
|
|||
const data = await res.json<{ error: string }>()
|
||||
assert(data.error.includes('Limit exceeded'))
|
||||
})
|
||||
|
||||
test('delete non-existing status', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
const mastodonId = 'abcd'
|
||||
const res = await statuses_id.handleRequestDelete(db, mastodonId, actor, domain)
|
||||
assert.equal(res.status, 404)
|
||||
})
|
||||
|
||||
test('delete status from a different actor', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
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)
|
||||
assert.equal(res.status, 404)
|
||||
})
|
||||
|
||||
test('delete status', async () => {
|
||||
const db = await makeDB()
|
||||
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||
const note = await createPublicNote(domain, db, 'note from actor', actor)
|
||||
|
||||
const res = await statuses_id.handleRequestDelete(db, note[mastodonIdSymbol]!, actor, domain)
|
||||
assert.equal(res.status, 200)
|
||||
|
||||
{
|
||||
const { count } = await db.prepare(`SELECT count(*) as count FROM outbox_objects`).first<any>()
|
||||
assert.equal(count, 0)
|
||||
}
|
||||
{
|
||||
const { count } = await db.prepare(`SELECT count(*) as count FROM objects`).first<any>()
|
||||
assert.equal(count, 0)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,17 +1,27 @@
|
|||
// https://docs.joinmastodon.org/methods/statuses/#get
|
||||
|
||||
import { type Note } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||
import { cors } from 'wildebeest/backend/src/utils/cors'
|
||||
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
|
||||
import type { UUID } from 'wildebeest/backend/src/types'
|
||||
import type { ContextData } from 'wildebeest/backend/src/types/context'
|
||||
import { getMastodonStatusById } from 'wildebeest/backend/src/mastodon/status'
|
||||
import { getMastodonStatusById, toMastodonStatusFromObject } from 'wildebeest/backend/src/mastodon/status'
|
||||
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||
import * as errors from 'wildebeest/backend/src/errors'
|
||||
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
|
||||
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
|
||||
|
||||
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, env, request }) => {
|
||||
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ params, env, request }) => {
|
||||
const domain = new URL(request.url).hostname
|
||||
return handleRequest(env.DATABASE, params.id as UUID, domain)
|
||||
return handleRequestGet(env.DATABASE, params.id as UUID, domain)
|
||||
}
|
||||
|
||||
export async function handleRequest(db: D1Database, id: UUID, domain: string): Promise<Response> {
|
||||
export const onRequestDelete: PagesFunction<Env, any, ContextData> = async ({ params, env, request, data }) => {
|
||||
const domain = new URL(request.url).hostname
|
||||
return handleRequestDelete(env.DATABASE, params.id as UUID, data.connectedActor, domain)
|
||||
}
|
||||
|
||||
export async function handleRequestGet(db: D1Database, id: UUID, domain: string): Promise<Response> {
|
||||
const status = await getMastodonStatusById(db, id, domain)
|
||||
if (status === null) {
|
||||
return new Response('', { status: 404 })
|
||||
|
@ -23,3 +33,49 @@ export async function handleRequest(db: D1Database, id: UUID, domain: string): P
|
|||
}
|
||||
return new Response(JSON.stringify(status), { headers })
|
||||
}
|
||||
|
||||
// TODO: eventually use SQLite's `ON DELETE CASCADE` but requires writing the DB
|
||||
// schema directly into D1, which D1 disallows at the moment.
|
||||
// Some context at: https://stackoverflow.com/questions/13150075/add-on-delete-cascade-behavior-to-an-sqlite3-table-after-it-has-been-created
|
||||
async function deleteNote(db: D1Database, note: Note) {
|
||||
const deleteOutboxObject = db.prepare('DELETE FROM outbox_objects WHERE id=?').bind(note.id.toString())
|
||||
const deleteObject = db.prepare('DELETE FROM objects WHERE id=?').bind(note.id.toString())
|
||||
|
||||
const res = await db.batch([deleteOutboxObject, deleteObject])
|
||||
|
||||
for (let i = 0, len = res.length; i < len; i++) {
|
||||
if (!res[i].success) {
|
||||
throw new Error('SQL error: ' + res[i].error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleRequestDelete(
|
||||
db: D1Database,
|
||||
id: UUID,
|
||||
connectedActor: Person,
|
||||
domain: string
|
||||
): Promise<Response> {
|
||||
const obj = (await getObjectByMastodonId(db, id)) as Note
|
||||
if (obj === null) {
|
||||
return errors.statusNotFound(id)
|
||||
}
|
||||
|
||||
const status = await toMastodonStatusFromObject(db, obj, domain)
|
||||
if (status === null) {
|
||||
return errors.statusNotFound(id)
|
||||
}
|
||||
if (status.account.id !== urlToHandle(connectedActor.id)) {
|
||||
return errors.statusNotFound(id)
|
||||
}
|
||||
|
||||
await deleteNote(db, obj)
|
||||
|
||||
// FIXME: deliver a Delete message to the Actor followers and our peers
|
||||
|
||||
const headers = {
|
||||
...cors(),
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
}
|
||||
return new Response(JSON.stringify(status), { headers })
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue