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 { z } from 'zod';
import { type AppController } from '@/app.ts'; import { type AppController } from '@/app.ts';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import { booleanParamSchema } from '@/schema.ts'; import { booleanParamSchema } from '@/schema.ts';
import { Storages } from '@/storages.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 { 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({ const adminAccountQuerySchema = z.object({
local: booleanParamSchema.optional(), local: booleanParamSchema.optional(),
@ -27,62 +28,83 @@ const adminAccountQuerySchema = z.object({
}); });
const adminAccountsController: AppController = async (c) => { const adminAccountsController: AppController = async (c) => {
const store = await Storages.db();
const params = paginationSchema.parse(c.req.query());
const { signal } = c.req.raw;
const { const {
local,
pending, pending,
disabled, disabled,
silenced, silenced,
suspended, suspended,
sensitized, sensitized,
staff,
} = adminAccountQuerySchema.parse(c.req.query()); } = 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) { if (pending) {
for (const event of await store.query([{ kinds: [3036], '#p': [Conf.pubkey], ...params }], { signal })) { if (disabled || silenced || suspended || sensitized) {
pubkeys.add(event.pubkey); return c.json([]);
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);
}
} }
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 }) if (disabled || silenced || suspended || sensitized) {
.then((events) => hydrateEvents({ store, events, signal })); const n = [];
const accounts = await Promise.all( if (disabled) {
[...pubkeys].map(async (pubkey) => { n.push('disabled');
const author = authors.find((event) => event.pubkey === pubkey); }
const account = author ? await renderAdminAccount(author) : await renderAdminAccountFromPubkey(pubkey); if (silenced) {
const request = events.find((event) => event.kind === 3036 && event.pubkey === pubkey); n.push('silenced');
const grant = events.find( }
(event) => event.kind === 30360 && event.tags.find(([name]) => name === 'd')?.[1] === pubkey, if (suspended) {
); n.push('suspended');
}
if (sensitized) {
n.push('sensitized');
}
if (staff) {
n.push('admin');
n.push('moderator');
}
return { const events = await store.query([{ kinds: [30382], authors: [Conf.pubkey], '#n': n, ...params }], { signal });
...account, const pubkeys = new Set<string>(events.map(({ pubkey }) => pubkey));
invite_request: request?.content ?? null, const authors = await store.query([{ kinds: [0], authors: [...pubkeys] }])
invite_request_username: request?.tags.find(([name]) => name === 'r')?.[1] ?? null, .then((events) => hydrateEvents({ store, events, signal }));
approved: !!grant,
};
}),
);
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); return paginated(c, events, accounts);
}; };
@ -104,7 +126,7 @@ const adminActionController: AppController = async (c) => {
const n: Record<string, boolean> = {}; const n: Record<string, boolean> = {};
if (data.type === 'sensitive') { if (data.type === 'sensitive') {
n.sensitive = true; n.sensitized = true;
} }
if (data.type === 'disable') { if (data.type === 'disable') {
n.disabled = true; n.disabled = true;

View File

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

View File

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