Merge branch 'admin-accounts' into 'main'
Support admin accounts endpoint (first pass) See merge request soapbox-pub/ditto!96
This commit is contained in:
commit
eee6ccebab
|
@ -35,6 +35,7 @@ import {
|
||||||
updateCredentialsController,
|
updateCredentialsController,
|
||||||
verifyCredentialsController,
|
verifyCredentialsController,
|
||||||
} from './controllers/api/accounts.ts';
|
} from './controllers/api/accounts.ts';
|
||||||
|
import { adminAccountsController } from './controllers/api/admin.ts';
|
||||||
import { appCredentialsController, createAppController } from './controllers/api/apps.ts';
|
import { appCredentialsController, createAppController } from './controllers/api/apps.ts';
|
||||||
import { blocksController } from './controllers/api/blocks.ts';
|
import { blocksController } from './controllers/api/blocks.ts';
|
||||||
import { bookmarksController } from './controllers/api/bookmarks.ts';
|
import { bookmarksController } from './controllers/api/bookmarks.ts';
|
||||||
|
@ -185,6 +186,7 @@ app.get('/api/v1/favourites', requirePubkey, favouritesController);
|
||||||
app.get('/api/v1/bookmarks', requirePubkey, bookmarksController);
|
app.get('/api/v1/bookmarks', requirePubkey, bookmarksController);
|
||||||
app.get('/api/v1/blocks', requirePubkey, blocksController);
|
app.get('/api/v1/blocks', requirePubkey, blocksController);
|
||||||
|
|
||||||
|
app.get('/api/v1/admin/accounts', adminAccountsController);
|
||||||
app.post('/api/v1/pleroma/admin/config', requireRole('admin'), updateConfigController);
|
app.post('/api/v1/pleroma/admin/config', requireRole('admin'), updateConfigController);
|
||||||
|
|
||||||
// Not (yet) implemented.
|
// Not (yet) implemented.
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { type AppController } from '@/app.ts';
|
||||||
|
import { Conf } from '@/config.ts';
|
||||||
|
import { z } from '@/deps.ts';
|
||||||
|
import { booleanParamSchema } from '@/schema.ts';
|
||||||
|
import { eventsDB } from '@/storages.ts';
|
||||||
|
import { renderAdminAccount } from '@/views/mastodon/admin-accounts.ts';
|
||||||
|
import { paginated, paginationSchema } from '@/utils/api.ts';
|
||||||
|
|
||||||
|
const adminAccountQuerySchema = z.object({
|
||||||
|
local: booleanParamSchema.optional(),
|
||||||
|
remote: booleanParamSchema.optional(),
|
||||||
|
active: booleanParamSchema.optional(),
|
||||||
|
pending: booleanParamSchema.optional(),
|
||||||
|
disabled: booleanParamSchema.optional(),
|
||||||
|
silenced: booleanParamSchema.optional(),
|
||||||
|
suspended: booleanParamSchema.optional(),
|
||||||
|
sensitized: booleanParamSchema.optional(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
display_name: z.string().optional(),
|
||||||
|
by_domain: z.string().optional(),
|
||||||
|
email: z.string().optional(),
|
||||||
|
ip: z.string().optional(),
|
||||||
|
staff: booleanParamSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminAccountsController: AppController = async (c) => {
|
||||||
|
const {
|
||||||
|
pending,
|
||||||
|
disabled,
|
||||||
|
silenced,
|
||||||
|
suspended,
|
||||||
|
sensitized,
|
||||||
|
} = adminAccountQuerySchema.parse(c.req.query());
|
||||||
|
|
||||||
|
// Not supported.
|
||||||
|
if (pending || disabled || silenced || suspended || sensitized) {
|
||||||
|
return c.json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { since, until, limit } = paginationSchema.parse(c.req.query());
|
||||||
|
|
||||||
|
const events = await eventsDB.getEvents([{ kinds: [30361], authors: [Conf.pubkey], since, until, limit }]);
|
||||||
|
const pubkeys = events.map((event) => event.tags.find(([name]) => name === 'd')?.[1]!);
|
||||||
|
const authors = await eventsDB.getEvents([{ kinds: [0], authors: pubkeys }]);
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
const d = event.tags.find(([name]) => name === 'd')?.[1];
|
||||||
|
event.d_author = authors.find((author) => author.pubkey === d);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = await Promise.all(
|
||||||
|
events.map((event) => renderAdminAccount(event)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return paginated(c, events, accounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { adminAccountsController };
|
|
@ -1,7 +1,7 @@
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { type DittoDB } from '@/db.ts';
|
import { type DittoDB } from '@/db.ts';
|
||||||
import { Debug, type Event, Kysely, type SelectQueryBuilder } from '@/deps.ts';
|
import { Debug, type Event, Kysely, type SelectQueryBuilder } from '@/deps.ts';
|
||||||
import { type DittoFilter } from '@/filter.ts';
|
import { type DittoFilter, normalizeFilters } from '@/filter.ts';
|
||||||
import { isDittoInternalKind, isParameterizedReplaceableKind, isReplaceableKind } from '@/kinds.ts';
|
import { isDittoInternalKind, isParameterizedReplaceableKind, isReplaceableKind } from '@/kinds.ts';
|
||||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||||
import { isNostrId, isURL } from '@/utils.ts';
|
import { isNostrId, isURL } from '@/utils.ts';
|
||||||
|
@ -264,12 +264,12 @@ class EventsDB implements EventStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get events for filters from the database. */
|
/** Get events for filters from the database. */
|
||||||
async getEvents<K extends number>(
|
async getEvents<K extends number>(filters: DittoFilter<K>[], opts: GetEventsOpts = {}): Promise<DittoEvent<K>[]> {
|
||||||
filters: DittoFilter<K>[],
|
filters = normalizeFilters(filters); // Improves performance of `{ kinds: [0], authors: ['...'] }` queries.
|
||||||
opts: GetEventsOpts = {},
|
|
||||||
): Promise<DittoEvent<K>[]> {
|
|
||||||
if (opts.signal?.aborted) return Promise.resolve([]);
|
if (opts.signal?.aborted) return Promise.resolve([]);
|
||||||
if (!filters.length) return Promise.resolve([]);
|
if (!filters.length) return Promise.resolve([]);
|
||||||
|
|
||||||
this.#debug('REQ', JSON.stringify(filters));
|
this.#debug('REQ', JSON.stringify(filters));
|
||||||
let query = this.getEventsQuery(filters);
|
let query = this.getEventsQuery(filters);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ interface DittoEvent<K extends number = number> extends Event<K> {
|
||||||
author?: DittoEvent<0>;
|
author?: DittoEvent<0>;
|
||||||
author_stats?: AuthorStats;
|
author_stats?: AuthorStats;
|
||||||
event_stats?: EventStats;
|
event_stats?: EventStats;
|
||||||
|
d_author?: DittoEvent<0>;
|
||||||
|
user?: DittoEvent<30361>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Storage interface for Nostr events. */
|
/** Storage interface for Nostr events. */
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface ToAccountOpts {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderAccount(
|
async function renderAccount(
|
||||||
event: Omit<NonNullable<DittoEvent['author']>, 'id' | 'sig'>,
|
event: Omit<DittoEvent<0>, 'id' | 'sig'>,
|
||||||
opts: ToAccountOpts = {},
|
opts: ToAccountOpts = {},
|
||||||
) {
|
) {
|
||||||
const { withSource = false } = opts;
|
const { withSource = false } = opts;
|
||||||
|
@ -39,7 +39,7 @@ async function renderAccount(
|
||||||
avatar: picture,
|
avatar: picture,
|
||||||
avatar_static: picture,
|
avatar_static: picture,
|
||||||
bot: false,
|
bot: false,
|
||||||
created_at: nostrDate(event.created_at).toISOString(),
|
created_at: user ? user.inserted_at.toISOString() : nostrDate(event.created_at).toISOString(),
|
||||||
discoverable: true,
|
discoverable: true,
|
||||||
display_name: name,
|
display_name: name,
|
||||||
emojis: renderEmojis(event),
|
emojis: renderEmojis(event),
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { DittoEvent } from '@/storages/types.ts';
|
||||||
|
import { nostrDate } from '@/utils.ts';
|
||||||
|
|
||||||
|
import { accountFromPubkey, renderAccount } from './accounts.ts';
|
||||||
|
|
||||||
|
async function renderAdminAccount(event: DittoEvent<30361>) {
|
||||||
|
const d = event.tags.find(([name]) => name === 'd')?.[1]!;
|
||||||
|
const account = event.d_author ? await renderAccount({ ...event.d_author, user: event }) : await accountFromPubkey(d);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: account.id,
|
||||||
|
username: event.tags.find(([name]) => name === 'name')?.[1]!,
|
||||||
|
domain: account.acct.split('@')[1] || null,
|
||||||
|
created_at: nostrDate(event.created_at).toISOString(),
|
||||||
|
email: '',
|
||||||
|
ip: null,
|
||||||
|
ips: [],
|
||||||
|
locale: '',
|
||||||
|
invite_request: null,
|
||||||
|
role: event.tags.find(([name]) => name === 'role')?.[1] || 'user',
|
||||||
|
confirmed: true,
|
||||||
|
approved: true,
|
||||||
|
disabled: false,
|
||||||
|
silenced: false,
|
||||||
|
suspended: false,
|
||||||
|
account,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { renderAdminAccount };
|
Loading…
Reference in New Issue