import { NostrFilter, NSchema as n } from '@nostrify/nostrify'; import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { createEvent, paginated, 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 { booleanParamSchema } from '@/schema.ts'; const reportSchema = z.object({ account_id: n.id(), status_ids: n.id().array().default([]), comment: z.string().max(1000).default(''), category: z.string().default('other'), // TODO: rules_ids[] is not implemented }); /** https://docs.joinmastodon.org/methods/reports/#post */ const reportController: AppController = async (c) => { const store = c.get('store'); const body = await parseBody(c.req.raw); const result = reportSchema.safeParse(body); if (!result.success) { return c.json(result.error, 422); } const { account_id, status_ids, comment, category, } = result.data; const tags = [ ['p', account_id, category], ['P', Conf.pubkey], ]; for (const status of status_ids) { tags.push(['e', status, category]); } const event = await createEvent({ kind: 1984, content: comment, tags, }, c); await hydrateEvents({ events: [event], store }); 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 params = c.get('pagination'); const { resolved, account_id, target_account_id } = adminReportsSchema.parse(c.req.query()); const filter: NostrFilter = { kinds: [30383], authors: [Conf.pubkey], '#k': ['1984'], ...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) => renderAdminReport(event, { viewerPubkey })), ); return paginated(c, orig, reports); }; /** https://docs.joinmastodon.org/methods/admin/reports/#get-one */ const adminReportController: AppController = async (c) => { const eventId = c.req.param('id'); const { signal } = c.req.raw; const store = c.get('store'); const pubkey = await c.get('signer')?.getPublicKey(); const [event] = await store.query([{ kinds: [1984], ids: [eventId], limit: 1, }], { signal }); if (!event) { return c.json({ error: 'Not found' }, 404); } await hydrateEvents({ events: [event], store, signal }); const report = await renderAdminReport(event, { viewerPubkey: pubkey }); return c.json(report); }; /** https://docs.joinmastodon.org/methods/admin/reports/#resolve */ const adminReportResolveController: AppController = async (c) => { const eventId = c.req.param('id'); const { signal } = c.req.raw; const store = c.get('store'); const pubkey = await c.get('signer')?.getPublicKey(); const [event] = await store.query([{ kinds: [1984], ids: [eventId], limit: 1, }], { signal }); if (!event) { return c.json({ error: 'Not found' }, 404); } await updateEventInfo(eventId, { open: false, closed: true }, c); await hydrateEvents({ events: [event], store, signal }); const report = await renderAdminReport(event, { viewerPubkey: pubkey }); return c.json(report); }; const adminReportReopenController: AppController = async (c) => { const eventId = c.req.param('id'); const { signal } = c.req.raw; const store = c.get('store'); const pubkey = await c.get('signer')?.getPublicKey(); const [event] = await store.query([{ kinds: [1984], ids: [eventId], limit: 1, }], { signal }); if (!event) { return c.json({ error: 'Not found' }, 404); } await updateEventInfo(eventId, { open: true, closed: false }, c); await hydrateEvents({ events: [event], store, signal }); const report = await renderAdminReport(event, { viewerPubkey: pubkey }); return c.json(report); }; export { adminReportController, adminReportReopenController, adminReportResolveController, adminReportsController, reportController, };