Rework adminAccountsController to display pending accounts from nip05 requests

This commit is contained in:
Alex Gleason 2024-06-09 13:43:40 -05:00
parent 5379863d36
commit 07a380fb75
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
3 changed files with 95 additions and 64 deletions

View File

@ -1,13 +1,14 @@
import { NostrEvent } from '@nostrify/nostrify';
import { NostrFilter } from '@nostrify/nostrify';
import { z } from 'zod';
import { type AppController } from '@/app.ts';
import { Conf } from '@/config.ts';
import { booleanParamSchema } from '@/schema.ts';
import { Storages } from '@/storages.ts';
import { paginated, paginationSchema, parseBody, updateUser } from '@/utils/api.ts';
import { renderAdminAccount, renderAdminAccountFromPubkey } from '@/views/mastodon/admin-accounts.ts';
import { hydrateEvents } from '@/storages/hydrate.ts';
import { paginated, paginationSchema, parseBody, updateUser } from '@/utils/api.ts';
import { renderNameRequest } from '@/views/ditto.ts';
import { renderAdminAccount, renderAdminAccountFromPubkey } from '@/views/mastodon/admin-accounts.ts';
const adminAccountQuerySchema = z.object({
local: booleanParamSchema.optional(),
@ -27,62 +28,83 @@ const adminAccountQuerySchema = z.object({
});
const adminAccountsController: AppController = async (c) => {
const store = await Storages.db();
const params = paginationSchema.parse(c.req.query());
const { signal } = c.req.raw;
const {
local,
pending,
disabled,
silenced,
suspended,
sensitized,
staff,
} = adminAccountQuerySchema.parse(c.req.query());
// Not supported.
if (disabled || silenced || suspended || sensitized) {
return c.json([]);
}
const store = await Storages.db();
const params = paginationSchema.parse(c.req.query());
const { signal } = c.req.raw;
const pubkeys = new Set<string>();
const events: NostrEvent[] = [];
if (pending) {
for (const event of await store.query([{ kinds: [3036], '#p': [Conf.pubkey], ...params }], { signal })) {
pubkeys.add(event.pubkey);
events.push(event);
}
} else {
for (const event of await store.query([{ kinds: [30360], authors: [Conf.pubkey], ...params }], { signal })) {
const pubkey = event.tags.find(([name]) => name === 'd')?.[1];
if (pubkey) {
pubkeys.add(pubkey);
events.push(event);
}
if (disabled || silenced || suspended || sensitized) {
return c.json([]);
}
const orig = await store.query(
[{ kinds: [30383], authors: [Conf.pubkey], '#k': ['3036'], ...params }],
{ signal },
);
const ids = new Set<string>(
orig
.map(({ tags }) => tags.find(([name]) => name === 'd')?.[1])
.filter((id): id is string => !!id),
);
const events = await store.query([{ kinds: [3036], ids: [...ids] }])
.then((events) => hydrateEvents({ store, events, signal }));
const nameRequests = await Promise.all(events.map(renderNameRequest));
return paginated(c, orig, nameRequests);
}
const authors = await store.query([{ kinds: [0], authors: [...pubkeys] }], { signal })
.then((events) => hydrateEvents({ store, events, signal }));
if (disabled || silenced || suspended || sensitized) {
const n = [];
const accounts = await Promise.all(
[...pubkeys].map(async (pubkey) => {
const author = authors.find((event) => event.pubkey === pubkey);
const account = author ? await renderAdminAccount(author) : await renderAdminAccountFromPubkey(pubkey);
const request = events.find((event) => event.kind === 3036 && event.pubkey === pubkey);
const grant = events.find(
(event) => event.kind === 30360 && event.tags.find(([name]) => name === 'd')?.[1] === pubkey,
);
if (disabled) {
n.push('disabled');
}
if (silenced) {
n.push('silenced');
}
if (suspended) {
n.push('suspended');
}
if (sensitized) {
n.push('sensitized');
}
if (staff) {
n.push('admin');
n.push('moderator');
}
return {
...account,
invite_request: request?.content ?? null,
invite_request_username: request?.tags.find(([name]) => name === 'r')?.[1] ?? null,
approved: !!grant,
};
}),
);
const events = await store.query([{ kinds: [30382], authors: [Conf.pubkey], '#n': n, ...params }], { signal });
const pubkeys = new Set<string>(events.map(({ pubkey }) => pubkey));
const authors = await store.query([{ kinds: [0], authors: [...pubkeys] }])
.then((events) => hydrateEvents({ store, events, signal }));
const accounts = await Promise.all(
[...pubkeys].map((pubkey) => {
const author = authors.find((e) => e.pubkey === pubkey);
return author ? renderAdminAccount(author) : renderAdminAccountFromPubkey(pubkey);
}),
);
return paginated(c, events, accounts);
}
const filter: NostrFilter = { kinds: [0], ...params };
if (local) {
filter.search = `domain:${Conf.url.host}`;
}
const events = await store.query([filter], { signal });
const accounts = await Promise.all(events.map(renderAdminAccount));
return paginated(c, events, accounts);
};
@ -104,7 +126,7 @@ const adminActionController: AppController = async (c) => {
const n: Record<string, boolean> = {};
if (data.type === 'sensitive') {
n.sensitive = true;
n.sensitized = true;
}
if (data.type === 'disable') {
n.disabled = true;

View File

@ -1,25 +1,22 @@
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
import { getTagSet } from '@/utils/tags.ts';
import { renderAdminAccount, renderAdminAccountFromPubkey } from '@/views/mastodon/admin-accounts.ts';
/** Renders an Admin::Account entity from a name request event. */
export async function renderNameRequest(event: DittoEvent) {
const n = getTagSet(event.info?.tags ?? [], 'n');
const [username, domain] = event.tags.find(([name]) => name === 'r')?.[1]?.split('@') ?? [];
let approvalStatus = 'pending';
if (n.has('approved')) {
approvalStatus = 'approved';
}
if (n.has('rejected')) {
approvalStatus = 'rejected';
}
const adminAccount = event.author
? await renderAdminAccount(event.author)
: await renderAdminAccountFromPubkey(event.pubkey);
return {
...adminAccount,
id: event.id,
account: event.author ? await renderAccount(event.author) : accountFromPubkey(event.pubkey),
name: event.tags.find(([name]) => name === 'r')?.[1] || '',
reason: event.content,
approval_status: approvalStatus,
created_at: new Date(event.created_at * 1000).toISOString(),
approved: n.has('approved'),
username,
domain,
invite_request: event.content,
};
}

View File

@ -1,9 +1,20 @@
import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts';
import { type DittoEvent } from '@/interfaces/DittoEvent.ts';
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
import { getTagSet } from '@/utils/tags.ts';
/** Expects a kind 0 fully hydrated */
async function renderAdminAccount(event: DittoEvent) {
const account = await renderAccount(event);
const names = getTagSet(event.user?.tags ?? [], 'n');
let role = 'user';
if (names.has('admin')) {
role = 'admin';
}
if (names.has('moderator')) {
role = 'moderator';
}
return {
id: account.id,
@ -15,12 +26,13 @@ async function renderAdminAccount(event: DittoEvent) {
ips: [],
locale: '',
invite_request: null,
role: event.tags.find(([name]) => name === 'role')?.[1],
role,
confirmed: true,
approved: true,
disabled: false,
silenced: false,
suspended: false,
disabled: names.has('disabled'),
silenced: names.has('silenced'),
suspended: names.has('suspended'),
sensitized: names.has('sensitized'),
account,
};
}