From bd6424acf53ef73d14205ef4bdd1356818ffb3bf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 8 Jun 2024 22:16:34 -0500 Subject: [PATCH] Add preliminary nameRequestsController --- src/app.ts | 4 ++-- src/controllers/api/ditto.ts | 29 ++++++++++++++++++----- src/interfaces/DittoEvent.ts | 2 ++ src/pipeline.ts | 2 +- src/storages/hydrate.ts | 46 ++++++++++++++++++++++++++++-------- src/views/ditto.ts | 25 ++++++++++++++++++++ 6 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 src/views/ditto.ts diff --git a/src/app.ts b/src/app.ts index 198dc7f6..19641d45 100644 --- a/src/app.ts +++ b/src/app.ts @@ -30,7 +30,7 @@ import { adminAccountsController, adminActionController } from '@/controllers/ap import { appCredentialsController, createAppController } from '@/controllers/api/apps.ts'; import { blocksController } from '@/controllers/api/blocks.ts'; import { bookmarksController } from '@/controllers/api/bookmarks.ts'; -import { adminRelaysController, adminSetRelaysController, inviteRequestController } from '@/controllers/api/ditto.ts'; +import { adminRelaysController, adminSetRelaysController, nameRequestController } from '@/controllers/api/ditto.ts'; import { emptyArrayController, emptyObjectController, notImplementedController } from '@/controllers/api/fallback.ts'; import { instanceController } from '@/controllers/api/instance.ts'; import { markersController, updateMarkersController } from '@/controllers/api/markers.ts'; @@ -243,7 +243,7 @@ app.delete('/api/v1/pleroma/admin/statuses/:id', requireRole('admin'), pleromaAd app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysController); app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController); -app.post('/api/v1/ditto/nip05', requireSigner, inviteRequestController); +app.post('/api/v1/ditto/names', requireSigner, nameRequestController); app.post('/api/v1/ditto/zap', requireSigner, zapController); app.post('/api/v1/reports', requireSigner, reportController); diff --git a/src/controllers/api/ditto.ts b/src/controllers/api/ditto.ts index e6f398db..4cf2cd45 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -3,9 +3,11 @@ import { z } from 'zod'; import { AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; -import { Storages } from '@/storages.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; +import { Storages } from '@/storages.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; import { createEvent } from '@/utils/api.ts'; +import { renderNameRequest } from '@/views/ditto.ts'; const markerSchema = z.enum(['read', 'write']); @@ -60,15 +62,15 @@ function renderRelays(event: NostrEvent): RelayEntity[] { }, [] as RelayEntity[]); } -const inviteRequestSchema = z.object({ +const nameRequestSchema = z.object({ nip05: z.string().email(), reason: z.string().max(500).optional(), }); -export const inviteRequestController: AppController = async (c) => { - const { nip05, reason } = inviteRequestSchema.parse(await c.req.json()); +export const nameRequestController: AppController = async (c) => { + const { nip05, reason } = nameRequestSchema.parse(await c.req.json()); - await createEvent({ + const event = await createEvent({ kind: 3036, content: reason, tags: [ @@ -79,5 +81,20 @@ export const inviteRequestController: AppController = async (c) => { ], }, c); - return new Response(null, { status: 204 }); + await hydrateEvents({ events: [event], store: await Storages.db() }); + + const nameRequest = await renderNameRequest(event); + return c.json(nameRequest); +}; + +export const nameRequestsController: AppController = async (c) => { + const store = await Storages.db(); + const signer = c.get('signer')!; + const pubkey = await signer.getPublicKey(); + + const events = await store.query([{ kinds: [3036], authors: [pubkey], limit: 20 }]) + .then((events) => hydrateEvents({ events, store })); + + const nameRequests = await Promise.all(events.map(renderNameRequest)); + return c.json(nameRequests); }; diff --git a/src/interfaces/DittoEvent.ts b/src/interfaces/DittoEvent.ts index fea8e1ec..85e11d65 100644 --- a/src/interfaces/DittoEvent.ts +++ b/src/interfaces/DittoEvent.ts @@ -34,4 +34,6 @@ export interface DittoEvent extends NostrEvent { * https://github.com/nostr-protocol/nips/blob/master/56.md */ reported_notes?: DittoEvent[]; + /** Admin event relationship. */ + info?: DittoEvent; } diff --git a/src/pipeline.ts b/src/pipeline.ts index 7cdaa60a..20fed92d 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -212,7 +212,7 @@ async function generateSetEvents(event: NostrEvent): Promise { ['d', event.id], ['p', event.pubkey], ['k', '3036'], - ['n', 'open'], + ['n', 'pending'], ], created_at: Math.floor(Date.now() / 1000), }); diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index ded03d42..a3821aa3 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -44,6 +44,10 @@ async function hydrateEvents(opts: HydrateOpts): Promise { cache.push(event); } + for (const event of await gatherInfo({ events: cache, store, signal })) { + cache.push(event); + } + for (const event of await gatherReportedProfiles({ events: cache, store, signal })) { cache.push(event); } @@ -83,6 +87,7 @@ export function assembleEvents( for (const event of a) { event.author = b.find((e) => matchFilter({ kinds: [0], authors: [event.pubkey] }, e)); event.user = b.find((e) => matchFilter({ kinds: [30382], authors: [admin], '#d': [event.pubkey] }, e)); + event.info = b.find((e) => matchFilter({ kinds: [30383], authors: [admin], '#d': [event.id] }, e)); if (event.kind === 1) { const id = findQuoteTag(event.tags)?.[1] || findQuoteInContent(event.content); @@ -106,20 +111,21 @@ export function assembleEvents( } if (event.kind === 1984) { - const targetAccountId = event.tags.find(([name]) => name === 'p')?.[1]; - if (targetAccountId) { - event.reported_profile = b.find((e) => matchFilter({ kinds: [0], authors: [targetAccountId] }, e)); + const pubkey = event.tags.find(([name]) => name === 'p')?.[1]; + if (pubkey) { + event.reported_profile = b.find((e) => matchFilter({ kinds: [0], authors: [pubkey] }, e)); } - const reportedEvents: DittoEvent[] = []; - const status_ids = event.tags.filter(([name]) => name === 'e').map((tag) => tag[1]); - if (status_ids.length > 0) { - for (const id of status_ids) { - const reportedEvent = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); - if (reportedEvent) reportedEvents.push(reportedEvent); + const reportedEvents: DittoEvent[] = []; + const ids = event.tags.filter(([name]) => name === 'e').map(([_name, value]) => value); + + for (const id of ids) { + const reported = b.find((e) => matchFilter({ kinds: [1], ids: [id] }, e)); + if (reported) { + reportedEvents.push(reported); } - event.reported_notes = reportedEvents; } + event.reported_notes = reportedEvents; } event.author_stats = stats.authors.find((stats) => stats.pubkey === event.pubkey); @@ -206,6 +212,26 @@ function gatherUsers({ events, store, signal }: HydrateOpts): Promise { + const ids = new Set(); + + for (const event of events) { + if (event.kind === 3036) { + ids.add(event.id); + } + } + + if (!ids.size) { + return Promise.resolve([]); + } + + return store.query( + [{ ids: [...ids], limit: ids.size }], + { signal }, + ); +} + /** Collect reported notes from the events. */ function gatherReportedNotes({ events, store, signal }: HydrateOpts): Promise { const ids = new Set(); diff --git a/src/views/ditto.ts b/src/views/ditto.ts new file mode 100644 index 00000000..708c5223 --- /dev/null +++ b/src/views/ditto.ts @@ -0,0 +1,25 @@ +import { DittoEvent } from '@/interfaces/DittoEvent.ts'; +import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; +import { getTagSet } from '@/utils/tags.ts'; + +export async function renderNameRequest(event: DittoEvent) { + const n = getTagSet(event.info?.tags ?? [], 'n'); + + let approvalStatus = 'pending'; + + if (n.has('approved')) { + approvalStatus = 'approved'; + } + if (n.has('rejected')) { + approvalStatus = 'rejected'; + } + + return { + id: event.id, + account: event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey), + name: event.tags.find(([name]) => name === 'r')?.[1] || '', + reason: event.content, + approval_status: approvalStatus, + created_at: new Date(event.created_at * 1000).toISOString(), + }; +}