Move /api/v1/ditto/names to a DittoRoute

This commit is contained in:
Alex Gleason 2025-03-03 11:50:49 -06:00
parent 59ad40fb3f
commit a21ec4600a
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3 changed files with 113 additions and 105 deletions

View file

@ -55,8 +55,6 @@ import {
adminSetRelaysController, adminSetRelaysController,
deleteZapSplitsController, deleteZapSplitsController,
getZapSplitsController, getZapSplitsController,
nameRequestController,
nameRequestsController,
statusZapSplitsController, statusZapSplitsController,
updateInstanceController, updateInstanceController,
updateZapSplitsController, updateZapSplitsController,
@ -149,6 +147,7 @@ import { rateLimitMiddleware } from '@/middleware/rateLimitMiddleware.ts';
import { uploaderMiddleware } from '@/middleware/uploaderMiddleware.ts'; import { uploaderMiddleware } from '@/middleware/uploaderMiddleware.ts';
import { translatorMiddleware } from '@/middleware/translatorMiddleware.ts'; import { translatorMiddleware } from '@/middleware/translatorMiddleware.ts';
import { logiMiddleware } from '@/middleware/logiMiddleware.ts'; import { logiMiddleware } from '@/middleware/logiMiddleware.ts';
import dittoNamesRoute from '@/routes/dittoNamesRoute.ts';
import { DittoRelayStore } from '@/storages/DittoRelayStore.ts'; import { DittoRelayStore } from '@/storages/DittoRelayStore.ts';
export interface AppEnv extends DittoEnv { export interface AppEnv extends DittoEnv {
@ -446,8 +445,7 @@ app.put('/api/v1/admin/ditto/relays', userMiddleware({ role: 'admin' }), adminSe
app.put('/api/v1/admin/ditto/instance', userMiddleware({ role: 'admin' }), updateInstanceController); app.put('/api/v1/admin/ditto/instance', userMiddleware({ role: 'admin' }), updateInstanceController);
app.post('/api/v1/ditto/names', userMiddleware(), nameRequestController); app.route('/api/v1/ditto/names', dittoNamesRoute);
app.get('/api/v1/ditto/names', userMiddleware(), nameRequestsController);
app.get('/api/v1/ditto/captcha', rateLimitMiddleware(3, Time.minutes(1)), captchaController); app.get('/api/v1/ditto/captcha', rateLimitMiddleware(3, Time.minutes(1)), captchaController);
app.post( app.post(

View file

@ -1,19 +1,17 @@
import { paginated } from '@ditto/mastoapi/pagination'; import { NostrEvent, NSchema as n } from '@nostrify/nostrify';
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
import { z } from 'zod'; import { z } from 'zod';
import { AppController } from '@/app.ts'; import { AppController } from '@/app.ts';
import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts';
import { getAuthor } from '@/queries.ts'; import { getAuthor } from '@/queries.ts';
import { addTag } from '@/utils/tags.ts'; import { addTag } from '@/utils/tags.ts';
import { createEvent, parseBody, updateAdminEvent } from '@/utils/api.ts'; import { parseBody, updateAdminEvent } from '@/utils/api.ts';
import { getInstanceMetadata } from '@/utils/instance.ts'; import { getInstanceMetadata } from '@/utils/instance.ts';
import { deleteTag } from '@/utils/tags.ts'; import { deleteTag } from '@/utils/tags.ts';
import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts'; import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts';
import { screenshotsSchema } from '@/schemas/nostr.ts'; import { screenshotsSchema } from '@/schemas/nostr.ts';
import { booleanParamSchema, percentageSchema } from '@/schema.ts'; import { percentageSchema } from '@/schema.ts';
import { hydrateEvents } from '@/storages/hydrate.ts'; import { hydrateEvents } from '@/storages/hydrate.ts';
import { renderNameRequest } from '@/views/ditto.ts';
import { accountFromPubkey } from '@/views/mastodon/accounts.ts'; import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
import { renderAccount } from '@/views/mastodon/accounts.ts'; import { renderAccount } from '@/views/mastodon/accounts.ts';
import { updateListAdminEvent } from '@/utils/api.ts'; import { updateListAdminEvent } from '@/utils/api.ts';
@ -81,102 +79,6 @@ function renderRelays(event: NostrEvent): RelayEntity[] {
}, [] as RelayEntity[]); }, [] as RelayEntity[]);
} }
const nameRequestSchema = z.object({
name: z.string().email(),
reason: z.string().max(500).optional(),
});
export const nameRequestController: AppController = async (c) => {
const { conf, relay, user } = c.var;
const pubkey = await user!.signer.getPublicKey();
const result = nameRequestSchema.safeParse(await c.req.json());
if (!result.success) {
return c.json({ error: 'Invalid username', schema: result.error }, 400);
}
const { name, reason } = result.data;
const [existing] = await relay.query([{ kinds: [3036], authors: [pubkey], '#r': [name.toLowerCase()], limit: 1 }]);
if (existing) {
return c.json({ error: 'Name request already exists' }, 400);
}
const r: string[][] = [['r', name]];
if (name !== name.toLowerCase()) {
r.push(['r', name.toLowerCase()]);
}
const event = await createEvent({
kind: 3036,
content: reason,
tags: [
...r,
['L', 'nip05.domain'],
['l', name.split('@')[1], 'nip05.domain'],
['p', await conf.signer.getPublicKey()],
],
}, c);
await hydrateEvents({ ...c.var, events: [event] });
const nameRequest = await renderNameRequest(event);
return c.json(nameRequest);
};
const nameRequestsSchema = z.object({
approved: booleanParamSchema.optional(),
rejected: booleanParamSchema.optional(),
});
export const nameRequestsController: AppController = async (c) => {
const { conf, relay, user } = c.var;
const pubkey = await user!.signer.getPublicKey();
const params = c.get('pagination');
const { approved, rejected } = nameRequestsSchema.parse(c.req.query());
const filter: NostrFilter = {
kinds: [30383],
authors: [await conf.signer.getPublicKey()],
'#k': ['3036'],
'#p': [pubkey],
...params,
};
if (approved) {
filter['#n'] = ['approved'];
}
if (rejected) {
filter['#n'] = ['rejected'];
}
const orig = await relay.query([filter]);
const ids = new Set<string>();
for (const event of orig) {
const d = event.tags.find(([name]) => name === 'd')?.[1];
if (d) {
ids.add(d);
}
}
if (!ids.size) {
return c.json([]);
}
const events = await relay.query([{ kinds: [3036], ids: [...ids], authors: [pubkey] }])
.then((events) => hydrateEvents({ ...c.var, events }));
const nameRequests = await Promise.all(
events.map((event) => renderNameRequest(event)),
);
return paginated(c, orig, nameRequests);
};
const zapSplitSchema = z.record( const zapSplitSchema = z.record(
n.id(), n.id(),
z.object({ z.object({

View file

@ -0,0 +1,108 @@
import { paginationMiddleware, userMiddleware } from '@ditto/mastoapi/middleware';
import { DittoRoute } from '@ditto/mastoapi/router';
import { z } from 'zod';
import { createEvent } from '@/utils/api.ts';
import { hydrateEvents } from '@/storages/hydrate.ts';
import { renderNameRequest } from '@/views/ditto.ts';
import { booleanParamSchema } from '@/schema.ts';
import { NostrFilter } from '@nostrify/nostrify';
const nameRequestSchema = z.object({
name: z.string().email(),
reason: z.string().max(500).optional(),
});
const route = new DittoRoute();
route.post('/', userMiddleware(), async (c) => {
const { conf, relay, user } = c.var;
const pubkey = await user!.signer.getPublicKey();
const result = nameRequestSchema.safeParse(await c.req.json());
if (!result.success) {
return c.json({ error: 'Invalid username', schema: result.error }, 400);
}
const { name, reason } = result.data;
const [existing] = await relay.query([{ kinds: [3036], authors: [pubkey], '#r': [name.toLowerCase()], limit: 1 }]);
if (existing) {
return c.json({ error: 'Name request already exists' }, 400);
}
const r: string[][] = [['r', name]];
if (name !== name.toLowerCase()) {
r.push(['r', name.toLowerCase()]);
}
const event = await createEvent({
kind: 3036,
content: reason,
tags: [
...r,
['L', 'nip05.domain'],
['l', name.split('@')[1], 'nip05.domain'],
['p', await conf.signer.getPublicKey()],
],
}, c);
await hydrateEvents({ ...c.var, events: [event] });
const nameRequest = await renderNameRequest(event);
return c.json(nameRequest);
});
const nameRequestsSchema = z.object({
approved: booleanParamSchema.optional(),
rejected: booleanParamSchema.optional(),
});
route.get('/', paginationMiddleware(), userMiddleware(), async (c) => {
const { conf, relay, user, pagination } = c.var;
const pubkey = await user!.signer.getPublicKey();
const { approved, rejected } = nameRequestsSchema.parse(c.req.query());
const filter: NostrFilter = {
kinds: [30383],
authors: [await conf.signer.getPublicKey()],
'#k': ['3036'],
'#p': [pubkey],
...pagination,
};
if (approved) {
filter['#n'] = ['approved'];
}
if (rejected) {
filter['#n'] = ['rejected'];
}
const orig = await relay.query([filter]);
const ids = new Set<string>();
for (const event of orig) {
const d = event.tags.find(([name]) => name === 'd')?.[1];
if (d) {
ids.add(d);
}
}
if (!ids.size) {
return c.json([]);
}
const events = await relay.query([{ kinds: [3036], ids: [...ids], authors: [pubkey] }])
.then((events) => hydrateEvents({ ...c.var, events }));
const nameRequests = await Promise.all(
events.map((event) => renderNameRequest(event)),
);
return c.var.paginate(orig, nameRequests);
});
export default route;