From a14515bbe010eee99e601c7fa9c7a6749feb85fa Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 8 Jun 2024 19:48:56 -0500 Subject: [PATCH] Rework reports with event sets --- src/controllers/api/reports.ts | 80 ++++++++++++++++++++++++++-------- src/storages/EventsDB.ts | 2 +- src/utils/api.ts | 17 ++++++-- 3 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/controllers/api/reports.ts b/src/controllers/api/reports.ts index 9cb2627..2092b8a 100644 --- a/src/controllers/api/reports.ts +++ b/src/controllers/api/reports.ts @@ -1,12 +1,14 @@ -import { NSchema as n } from '@nostrify/nostrify'; +import { NostrFilter, NSchema as n } from '@nostrify/nostrify'; import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; -import { createAdminEvent, createEvent, parseBody } from '@/utils/api.ts'; +import { createEvent, paginationSchema, parseBody, updateEventInfo } from '@/utils/api.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { renderAdminReport } from '@/views/mastodon/reports.ts'; import { renderReport } from '@/views/mastodon/reports.ts'; +import { getTagSet } from '@/utils/tags.ts'; +import { booleanParamSchema } from '@/schema.ts'; const reportSchema = z.object({ account_id: n.id(), @@ -52,18 +54,60 @@ const reportController: AppController = async (c) => { return c.json(await renderReport(event)); }; +const adminReportsSchema = z.object({ + resolved: booleanParamSchema.optional(), + account_id: n.id().optional(), + target_account_id: n.id().optional(), +}); + /** https://docs.joinmastodon.org/methods/admin/reports/#get */ const adminReportsController: AppController = async (c) => { const store = c.get('store'); const viewerPubkey = await c.get('signer')?.getPublicKey(); - const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }]) - .then((events) => hydrateEvents({ store, events: events, signal: c.req.raw.signal })) - .then((events) => - Promise.all( - events.map((event) => renderAdminReport(event, { viewerPubkey })), - ) - ); + const params = paginationSchema.parse(c.req.query()); + const { resolved, account_id, target_account_id } = adminReportsSchema.parse(c.req.query()); + + const filter: NostrFilter = { + kinds: [30383], + authors: [Conf.pubkey], + ...params, + }; + + if (typeof resolved === 'boolean') { + filter['#n'] = [resolved ? 'closed' : 'open']; + } + if (account_id) { + filter['#p'] = [account_id]; + } + if (target_account_id) { + filter['#P'] = [target_account_id]; + } + + const orig = await store.query([filter]); + const ids = new Set(); + + for (const event of orig) { + const d = event.tags.find(([name]) => name === 'd')?.[1]; + if (d) { + ids.add(d); + } + } + + const events = await store.query([{ kinds: [1984], ids: [...ids] }]) + .then((events) => hydrateEvents({ store, events: events, signal: c.req.raw.signal })); + + const reports = await Promise.all( + events.map((event) => { + const internal = orig.find(({ tags }) => tags.some(([name, value]) => name === 'd' && value === event.id)); + const names = getTagSet(internal?.tags ?? [], 'n'); + + return renderAdminReport(event, { + viewerPubkey, + actionTaken: names.has('closed'), + }); + }), + ); return c.json(reports); }; @@ -82,12 +126,13 @@ const adminReportController: AppController = async (c) => { }], { signal }); if (!event) { - return c.json({ error: 'This action is not allowed' }, 403); + return c.json({ error: 'Not found' }, 404); } await hydrateEvents({ events: [event], store, signal }); - return c.json(await renderAdminReport(event, { viewerPubkey: pubkey })); + const report = await renderAdminReport(event, { viewerPubkey: pubkey }); + return c.json(report); }; /** https://docs.joinmastodon.org/methods/admin/reports/#resolve */ @@ -104,18 +149,15 @@ const adminReportResolveController: AppController = async (c) => { }], { signal }); if (!event) { - return c.json({ error: 'This action is not allowed' }, 403); + return c.json({ error: 'Not found' }, 404); } + await updateEventInfo(eventId, { open: false, closed: true }, c); + await hydrateEvents({ events: [event], store, signal }); - await createAdminEvent({ - kind: 5, - tags: [['e', event.id]], - content: 'Report closed.', - }, c); - - return c.json(await renderAdminReport(event, { viewerPubkey: pubkey, actionTaken: true })); + const report = await renderAdminReport(event, { viewerPubkey: pubkey, actionTaken: true }); + return c.json(report); }; export { adminReportController, adminReportResolveController, adminReportsController, reportController }; diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 8dcee6c..6a94954 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -29,9 +29,9 @@ class EventsDB implements NStore { 'a': ({ count }) => count < 15, 'd': ({ event, count }) => count === 0 && NKinds.parameterizedReplaceable(event.kind), 'e': ({ event, count, value }) => ((event.kind === 10003) || count < 15) && isNostrId(value), + 'k': ({ count, value }) => count === 0 && Number.isInteger(Number(value)), 'L': ({ event, count }) => event.kind === 1985 || count === 0, 'l': ({ event, count }) => event.kind === 1985 || count === 0, - 'media': ({ count, value }) => (count < 4) && isURL(value), 'n': ({ count, value }) => count < 50 && value.length < 50, 'P': ({ count, value }) => count === 0 && isNostrId(value), 'p': ({ event, count, value }) => (count < 15 || event.kind === 3) && isNostrId(value), diff --git a/src/utils/api.ts b/src/utils/api.ts index a9390b5..1fa397b 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -107,12 +107,20 @@ async function updateAdminEvent( return createAdminEvent(fn(prev), c); } -async function updateUser(pubkey: string, n: Record, c: AppContext): Promise { +function updateUser(pubkey: string, n: Record, c: AppContext): Promise { + return updateNames(30382, pubkey, n, c); +} + +function updateEventInfo(id: string, n: Record, c: AppContext): Promise { + return updateNames(30383, id, n, c); +} + +async function updateNames(k: number, d: string, n: Record, c: AppContext): Promise { const signer = new AdminSigner(); const admin = await signer.getPublicKey(); return updateAdminEvent( - { kinds: [30382], authors: [admin], '#d': [pubkey], limit: 1 }, + { kinds: [k], authors: [admin], '#d': [d], limit: 1 }, (prev) => { const prevNames = prev?.tags.reduce((acc, [name, value]) => { if (name === 'n') acc[value] = true; @@ -124,10 +132,10 @@ async function updateUser(pubkey: string, n: Record, c: AppCont const other = prev?.tags.filter(([name]) => !['d', 'n'].includes(name)) ?? []; return { - kind: 30382, + kind: k, content: prev?.content ?? '', tags: [ - ['d', pubkey], + ['d', d], ...nTags, ...other, ], @@ -296,6 +304,7 @@ export { parseBody, updateAdminEvent, updateEvent, + updateEventInfo, updateListAdminEvent, updateListEvent, updateUser,