mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
Move /api/v1/ditto/names to a DittoRoute
This commit is contained in:
parent
59ad40fb3f
commit
a21ec4600a
3 changed files with 113 additions and 105 deletions
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
108
packages/ditto/routes/dittoNamesRoute.ts
Normal file
108
packages/ditto/routes/dittoNamesRoute.ts
Normal 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;
|
||||||
Loading…
Add table
Reference in a new issue