diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index 44b0b9b4..5bc27a42 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -85,6 +85,7 @@ import { pleromaAdminTagController, pleromaAdminUnsuggestController, pleromaAdminUntagController, + pleromaPromoteAdminController, updateConfigController, } from '@/controllers/api/pleroma.ts'; import { preferencesController } from '@/controllers/api/preferences.ts'; @@ -440,6 +441,11 @@ app.delete('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', userMi app.get('/api/v1/pleroma/admin/config', userMiddleware({ role: 'admin' }), configController); app.post('/api/v1/pleroma/admin/config', userMiddleware({ role: 'admin' }), updateConfigController); app.delete('/api/v1/pleroma/admin/statuses/:id', userMiddleware({ role: 'admin' }), pleromaAdminDeleteStatusController); +app.post( + '/api/v1/pleroma/admin/users/permission_group/admin', + userMiddleware({ role: 'admin' }), + pleromaPromoteAdminController, +); app.get('/api/v1/admin/ditto/relays', userMiddleware({ role: 'admin' }), adminRelaysController); app.put('/api/v1/admin/ditto/relays', userMiddleware({ role: 'admin' }), adminSetRelaysController); diff --git a/packages/ditto/controllers/api/pleroma.ts b/packages/ditto/controllers/api/pleroma.ts index b4458c6c..d42a7f3b 100644 --- a/packages/ditto/controllers/api/pleroma.ts +++ b/packages/ditto/controllers/api/pleroma.ts @@ -1,10 +1,14 @@ +import { nip19 } from 'nostr-tools'; import { z } from 'zod'; import { type AppController } from '@/app.ts'; -import { configSchema, elixirTupleSchema } from '@/schemas/pleroma-api.ts'; -import { createAdminEvent, updateAdminEvent, updateUser } from '@/utils/api.ts'; +import { createAdminEvent, parseBody, updateAdminEvent, updateUser } from '@/utils/api.ts'; +import { nostrNow } from '@/utils.ts'; import { lookupPubkey } from '@/utils/lookup.ts'; import { getPleromaConfigs } from '@/utils/pleroma.ts'; +import { renderAccount } from '@/views/mastodon/accounts.ts'; +import { configSchema, elixirTupleSchema } from '@/schemas/pleroma-api.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; const frontendConfigController: AppController = async (c) => { const configDB = await getPleromaConfigs(c.var); @@ -62,6 +66,93 @@ const pleromaAdminTagSchema = z.object({ tags: z.string().array(), }); +const pleromaPromoteAdminSchema = z.object({ + nicknames: z.string().transform((value, ctx) => { + try { + const { type, data } = nip19.decode(value); + if (type === 'npub') { + return data; + } + + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Not a valid nip 19 npub', + }); + + return z.NEVER; + } catch { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Not a valid nip 19 npub', + }); + + return z.NEVER; + } + }).array().min(1), +}); + +const pleromaPromoteAdminController: AppController = async (c) => { + const { conf, relay, signal } = c.var; + const body = await parseBody(c.req.raw); + const result = pleromaPromoteAdminSchema.safeParse(body); + + if (!result.success) { + return c.json({ error: 'Bad request', schema: result.error }, 422); + } + + const { data } = result; + const { nicknames: authors } = data; + + const events = await relay.query([{ kinds: [0], authors }], { signal }); + + if (events.length !== authors.length) { + return c.json({ error: 'User profile is missing in the database' }, 422); + } + + events.forEach(async (event) => { + const [existing] = await relay.query([{ + kinds: [30382], + authors: [await conf.signer.getPublicKey()], + '#d': [event.pubkey], + limit: 1, + }]); + + const prevTags = (existing?.tags ?? []).filter(([name, value]) => { + if (name === 'd') { + return false; + } + if (name === 'n' && value === 'admin') { + return false; + } + return true; + }); + + const tags: string[][] = [ + ['d', event.pubkey], + ['n', 'admin'], + ]; + + tags.push(...prevTags); + + const promotion = await conf.signer.signEvent({ + kind: 30382, + tags, + content: '', + created_at: nostrNow(), + }); + + await relay.event(promotion); + }); + + await hydrateEvents({ ...c.var, events }); + + const accounts = events.map((event) => { + return renderAccount(event); + }); + + return c.json(accounts, 200); +}; + const pleromaAdminTagController: AppController = async (c) => { const { conf } = c.var; const params = pleromaAdminTagSchema.parse(await c.req.json()); @@ -154,5 +245,6 @@ export { pleromaAdminTagController, pleromaAdminUnsuggestController, pleromaAdminUntagController, + pleromaPromoteAdminController, updateConfigController, };