mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
208 lines
5.9 KiB
TypeScript
208 lines
5.9 KiB
TypeScript
import { type AppController } from '@/app.ts';
|
|
import { type Filter, findReplyTag, z } from '@/deps.ts';
|
|
import * as mixer from '@/mixer.ts';
|
|
import { getAuthor, getFollowedPubkeys, getFollows, syncUser } from '@/queries.ts';
|
|
import { booleanParamSchema } from '@/schema.ts';
|
|
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
|
import { toAccount, toRelationship, toStatus } from '@/transformers/nostr-to-mastoapi.ts';
|
|
import { isFollowing, lookupAccount } from '@/utils.ts';
|
|
import { paginated, paginationSchema, parseBody } from '@/utils/web.ts';
|
|
import { createEvent } from '@/utils/web.ts';
|
|
import { renderEventAccounts } from '@/views.ts';
|
|
|
|
const createAccountController: AppController = (c) => {
|
|
return c.json({ error: 'Please log in with Nostr.' }, 405);
|
|
};
|
|
|
|
const verifyCredentialsController: AppController = async (c) => {
|
|
const pubkey = c.get('pubkey')!;
|
|
|
|
await syncUser(pubkey);
|
|
|
|
const event = await getAuthor(pubkey);
|
|
if (event) {
|
|
return c.json(await toAccount(event, { withSource: true }));
|
|
}
|
|
|
|
return c.json({ error: 'Could not find user.' }, 404);
|
|
};
|
|
|
|
const accountController: AppController = async (c) => {
|
|
const pubkey = c.req.param('pubkey');
|
|
|
|
const event = await getAuthor(pubkey);
|
|
if (event) {
|
|
return c.json(await toAccount(event));
|
|
}
|
|
|
|
return c.json({ error: 'Could not find user.' }, 404);
|
|
};
|
|
|
|
const accountLookupController: AppController = async (c) => {
|
|
const acct = c.req.query('acct');
|
|
|
|
if (!acct) {
|
|
return c.json({ error: 'Missing `acct` query parameter.' }, 422);
|
|
}
|
|
|
|
const event = await lookupAccount(decodeURIComponent(acct));
|
|
if (event) {
|
|
return c.json(await toAccount(event));
|
|
}
|
|
|
|
return c.json({ error: 'Could not find user.' }, 404);
|
|
};
|
|
|
|
const accountSearchController: AppController = async (c) => {
|
|
const q = c.req.query('q');
|
|
|
|
if (!q) {
|
|
return c.json({ error: 'Missing `q` query parameter.' }, 422);
|
|
}
|
|
|
|
const event = await lookupAccount(decodeURIComponent(q));
|
|
if (event) {
|
|
return c.json([await toAccount(event)]);
|
|
}
|
|
|
|
return c.json([]);
|
|
};
|
|
|
|
const relationshipsController: AppController = async (c) => {
|
|
const pubkey = c.get('pubkey')!;
|
|
const ids = z.array(z.string()).safeParse(c.req.queries('id[]'));
|
|
|
|
if (!ids.success) {
|
|
return c.json({ error: 'Missing `id[]` query parameters.' }, 422);
|
|
}
|
|
|
|
const result = await Promise.all(ids.data.map((id) => toRelationship(pubkey, id)));
|
|
|
|
return c.json(result);
|
|
};
|
|
|
|
const accountStatusesQuerySchema = z.object({
|
|
pinned: booleanParamSchema.optional(),
|
|
limit: z.coerce.number().nonnegative().transform((v) => Math.min(v, 40)).catch(20),
|
|
exclude_replies: booleanParamSchema.optional(),
|
|
tagged: z.string().optional(),
|
|
});
|
|
|
|
const accountStatusesController: AppController = async (c) => {
|
|
const pubkey = c.req.param('pubkey');
|
|
const { since, until } = paginationSchema.parse(c.req.query());
|
|
const { pinned, limit, exclude_replies, tagged } = accountStatusesQuerySchema.parse(c.req.query());
|
|
|
|
// Nostr doesn't support pinned statuses.
|
|
if (pinned) {
|
|
return c.json([]);
|
|
}
|
|
|
|
const filter: Filter<1> = { authors: [pubkey], kinds: [1], since, until, limit };
|
|
if (tagged) {
|
|
filter['#t'] = [tagged];
|
|
}
|
|
|
|
let events = await mixer.getFilters([filter]);
|
|
|
|
if (exclude_replies) {
|
|
events = events.filter((event) => !findReplyTag(event));
|
|
}
|
|
|
|
const statuses = await Promise.all(events.map((event) => toStatus(event, c.get('pubkey'))));
|
|
return paginated(c, events, statuses);
|
|
};
|
|
|
|
const fileSchema = z.custom<File>((value) => value instanceof File);
|
|
|
|
const updateCredentialsSchema = z.object({
|
|
display_name: z.string().optional(),
|
|
note: z.string().optional(),
|
|
avatar: fileSchema.optional(),
|
|
header: fileSchema.optional(),
|
|
locked: z.boolean().optional(),
|
|
bot: z.boolean().optional(),
|
|
discoverable: z.boolean().optional(),
|
|
});
|
|
|
|
const updateCredentialsController: AppController = async (c) => {
|
|
const pubkey = c.get('pubkey')!;
|
|
const body = await parseBody(c.req.raw);
|
|
const result = updateCredentialsSchema.safeParse(body);
|
|
|
|
if (!result.success) {
|
|
return c.json(result.error, 422);
|
|
}
|
|
|
|
const author = await getAuthor(pubkey);
|
|
if (!author) {
|
|
return c.json({ error: 'Could not find user.' }, 404);
|
|
}
|
|
|
|
const meta = jsonMetaContentSchema.parse(author.content);
|
|
meta.name = result.data.display_name ?? meta.name;
|
|
meta.about = result.data.note ?? meta.about;
|
|
|
|
const event = await createEvent({
|
|
kind: 0,
|
|
content: JSON.stringify(meta),
|
|
tags: [],
|
|
}, c);
|
|
|
|
const account = await toAccount(event);
|
|
return c.json(account);
|
|
};
|
|
|
|
const followController: AppController = async (c) => {
|
|
const sourcePubkey = c.get('pubkey')!;
|
|
const targetPubkey = c.req.param('pubkey');
|
|
|
|
const source = await getFollows(sourcePubkey);
|
|
|
|
if (!source || !isFollowing(source, targetPubkey)) {
|
|
await createEvent({
|
|
kind: 3,
|
|
content: '',
|
|
tags: [
|
|
...(source?.tags ?? []),
|
|
['p', targetPubkey],
|
|
],
|
|
}, c);
|
|
}
|
|
|
|
const relationship = await toRelationship(sourcePubkey, targetPubkey);
|
|
return c.json(relationship);
|
|
};
|
|
|
|
const followersController: AppController = (c) => {
|
|
const pubkey = c.req.param('pubkey');
|
|
const params = paginationSchema.parse(c.req.query());
|
|
return renderEventAccounts(c, [{ kinds: [3], '#p': [pubkey], ...params }]);
|
|
};
|
|
|
|
const followingController: AppController = async (c) => {
|
|
const pubkey = c.req.param('pubkey');
|
|
const pubkeys = await getFollowedPubkeys(pubkey);
|
|
|
|
// TODO: pagination by offset.
|
|
const accounts = await Promise.all(pubkeys.map(async (pubkey) => {
|
|
const event = await getAuthor(pubkey);
|
|
return event ? await toAccount(event) : undefined;
|
|
}));
|
|
|
|
return c.json(accounts.filter(Boolean));
|
|
};
|
|
|
|
export {
|
|
accountController,
|
|
accountLookupController,
|
|
accountSearchController,
|
|
accountStatusesController,
|
|
createAccountController,
|
|
followController,
|
|
followersController,
|
|
followingController,
|
|
relationshipsController,
|
|
updateCredentialsController,
|
|
verifyCredentialsController,
|
|
};
|