From 2a2f43d1062d19f3327717e17374fc1b500c7fcd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 9 Jun 2024 11:57:10 -0500 Subject: [PATCH] Add admin name approve/reject endpoints --- src/app.ts | 16 +++++++++- src/controllers/api/ditto.ts | 57 +++++++++++++++++++++++++++++++++++- src/storages/EventsDB.ts | 2 ++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index e58bd53..7bf29ca 100644 --- a/src/app.ts +++ b/src/app.ts @@ -30,7 +30,15 @@ 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, nameRequestController } from '@/controllers/api/ditto.ts'; +import { + adminNameApproveController, + adminNameRejectController, + adminNameRequestsController, + adminRelaysController, + adminSetRelaysController, + nameRequestController, + nameRequestsController, +} 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'; @@ -245,6 +253,12 @@ app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysControlle app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController); app.post('/api/v1/ditto/names', requireSigner, nameRequestController); +app.get('/api/v1/ditto/names', requireSigner, nameRequestsController); + +app.get('/api/v1/admin/ditto/names', requireRole('admin'), adminNameRequestsController); +app.post('/api/v1/admin/ditto/names/:id{[0-9a-f]{64}}/approve', requireRole('admin'), adminNameApproveController); +app.post('/api/v1/admin/ditto/names/:id{[0-9a-f]{64}}/reject', requireRole('admin'), adminNameRejectController); + 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 d1f9b0d..f8ac5f2 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -7,7 +7,7 @@ import { booleanParamSchema } from '@/schema.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; import { Storages } from '@/storages.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; -import { createEvent, paginated, paginationSchema } from '@/utils/api.ts'; +import { createAdminEvent, createEvent, paginated, paginationSchema, updateEventInfo } from '@/utils/api.ts'; import { renderNameRequest } from '@/views/ditto.ts'; const markerSchema = z.enum(['read', 'write']); @@ -183,3 +183,58 @@ export const adminNameRequestsController: AppController = async (c) => { return paginated(c, orig, nameRequests); }; + +export const adminNameApproveController: AppController = async (c) => { + const eventId = c.req.param('id'); + const store = await Storages.db(); + + const [event] = await store.query([{ kinds: [3036], ids: [eventId] }]); + if (!event) { + return c.json({ error: 'Event not found' }, 404); + } + + const r = event.tags.find(([name]) => name === 'r')?.[1]; + if (!r) { + return c.json({ error: 'NIP-05 not found' }, 404); + } + if (!z.string().email().safeParse(r).success) { + return c.json({ error: 'Invalid NIP-05' }, 400); + } + + const [existing] = await store.query([{ kinds: [30360], authors: [Conf.pubkey], '#d': [r], limit: 1 }]); + if (existing) { + return c.json({ error: 'NIP-05 already granted to another user' }, 400); + } + + await createAdminEvent({ + kind: 30360, + tags: [ + ['d', r], + ['L', 'nip05.domain'], + ['l', r.split('@')[1], 'nip05.domain'], + ['p', event.pubkey], + ], + }, c); + + await updateEventInfo(eventId, { pending: false, approved: true, rejected: false }, c); + await hydrateEvents({ events: [event], store }); + + const nameRequest = await renderNameRequest(event); + return c.json(nameRequest); +}; + +export const adminNameRejectController: AppController = async (c) => { + const eventId = c.req.param('id'); + const store = await Storages.db(); + + const [event] = await store.query([{ kinds: [3036], ids: [eventId] }]); + if (!event) { + return c.json({ error: 'Event not found' }, 404); + } + + await updateEventInfo(eventId, { pending: false, approved: false, rejected: true }, c); + await hydrateEvents({ events: [event], store }); + + const nameRequest = await renderNameRequest(event); + return c.json(nameRequest); +}; diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 6a94954..c26ebf1 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -223,6 +223,8 @@ class EventsDB implements NStore { return event.content; case 30009: return EventsDB.buildTagsSearchContent(event.tags.filter(([t]) => t !== 'alt')); + case 30360: + return event.tags.find(([name]) => name === 'd')?.[1] || ''; default: return ''; }