Rework adminAccountsController to display pending accounts from nip05 requests
This commit is contained in:
parent
5379863d36
commit
07a380fb75
|
@ -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;
|
||||||
|
|
|
@ -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(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue