mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 04:55:42 +09:00
feat(admin): move pending registrations to their own page (#1388)
This commit is contained in:
1 parent
97b2ac91ca
commit
abcb5cb814
31 files changed
+839
-328
No files matched your search
@@ -39,6 +39,7 @@ export const AdminAuditReadActions = {
|
||||
LIST_GUILD_MEMORY_STATS: 'list_guild_memory_stats',
|
||||
LIST_GUILD_STICKERS: 'list_guild_stickers',
|
||||
LIST_GUILD_THREADS: 'list_guild_threads',
|
||||
LIST_PENDING_REGISTRATIONS: 'list_pending_registrations',
|
||||
LIST_REPORT_REASONS: 'list_report_reasons',
|
||||
LIST_USER_APPLICATIONS: 'list_user_applications',
|
||||
LIST_USER_CHANGE_LOG: 'list_user_change_log',
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
InstanceEmailSmtpTestRequest,
|
||||
InstanceEmailSmtpTestResponse,
|
||||
PendingRegistrationActionRequest,
|
||||
PendingRegistrationListResponse,
|
||||
RegistrationUrlIdParam,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import {DomainMigrationConfigSchema} from '@fluxer/schema/src/domains/admin/DomainMigrationSchemas';
|
||||
@@ -98,7 +99,6 @@ async function buildInstanceConfigResponse(): Promise<InstanceConfigResponse> {
|
||||
experimentDelivery,
|
||||
registrationConfig,
|
||||
registrationUrls,
|
||||
pendingRegistrations,
|
||||
] = await Promise.all([
|
||||
instanceConfigRepository.getSsoConfig(),
|
||||
instanceConfigRepository.getGatewayRolloutConfig(),
|
||||
@@ -109,7 +109,6 @@ async function buildInstanceConfigResponse(): Promise<InstanceConfigResponse> {
|
||||
instanceConfigRepository.getExperimentDeliveryConfig(),
|
||||
instanceConfigRepository.getRegistrationConfig(),
|
||||
instanceConfigRepository.getRegistrationUrlsForAdmin(),
|
||||
instanceConfigRepository.getPendingRegistrations(),
|
||||
]);
|
||||
const [appPublic, policy, resolvedServices, integrations, media, billing, accountIdentity, accountIdentityLocked] =
|
||||
await Promise.all([
|
||||
@@ -148,7 +147,6 @@ async function buildInstanceConfigResponse(): Promise<InstanceConfigResponse> {
|
||||
registration: {
|
||||
...registrationConfig,
|
||||
urls: registrationUrls,
|
||||
pending_registrations: pendingRegistrations,
|
||||
},
|
||||
self_hosted: Config.instance.selfHosted,
|
||||
account_identity: {
|
||||
@@ -320,10 +318,7 @@ export function InstanceConfigAdminController(app: HonoApp) {
|
||||
targetType: 'instance_config',
|
||||
targetId: 0n,
|
||||
action: AdminAuditReadActions.GET_INSTANCE_CONFIG,
|
||||
metadata: {
|
||||
registration_url_count: response.registration.urls.length,
|
||||
pending_registration_count: response.registration.pending_registrations.length,
|
||||
},
|
||||
metadata: {registration_url_count: response.registration.urls.length},
|
||||
});
|
||||
return ctx.json(response);
|
||||
},
|
||||
@@ -738,18 +733,43 @@ export function InstanceConfigAdminController(app: HonoApp) {
|
||||
return ctx.json(await buildInstanceConfigResponse());
|
||||
},
|
||||
);
|
||||
app.get(
|
||||
'/admin/instance/pending-registrations',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
|
||||
requireAdminACL(AdminACLs.REGISTRATION_REVIEW),
|
||||
OpenAPI({
|
||||
operationId: 'list_admin_pending_registrations',
|
||||
summary: 'List pending registrations',
|
||||
description:
|
||||
'Lists every registration waiting for manual review, oldest first. Requires REGISTRATION_REVIEW permission.',
|
||||
responseSchema: PendingRegistrationListResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const pendingRegistrations = await instanceConfigRepository.getPendingRegistrations();
|
||||
await recordAdminRead(ctx, {
|
||||
targetType: 'instance_config',
|
||||
targetId: 0n,
|
||||
action: AdminAuditReadActions.LIST_PENDING_REGISTRATIONS,
|
||||
metadata: {pending_registration_count: pendingRegistrations.length},
|
||||
});
|
||||
return ctx.json({pending_registrations: pendingRegistrations});
|
||||
},
|
||||
);
|
||||
app.patch(
|
||||
'/admin/instance/pending-registrations/:user_id',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
|
||||
requireAdminACL(AdminACLs.INSTANCE_CONFIG_UPDATE),
|
||||
requireAdminACL(AdminACLs.REGISTRATION_REVIEW),
|
||||
Validator('param', UserIdParam),
|
||||
Validator('json', PendingRegistrationActionRequest),
|
||||
OpenAPI({
|
||||
operationId: 'update_admin_pending_registration',
|
||||
summary: 'Approve or reject a pending registration',
|
||||
description:
|
||||
'Decides a registration waiting for manual review. Approving removes its pending registration trait, rejecting also prevents the account from logging in. Requires INSTANCE_CONFIG_UPDATE permission.',
|
||||
responseSchema: InstanceConfigResponse,
|
||||
'Decides a registration waiting for manual review and returns the registrations still waiting. Approving removes its pending registration trait, rejecting also prevents the account from logging in. Requires REGISTRATION_REVIEW permission.',
|
||||
responseSchema: PendingRegistrationListResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
@@ -759,7 +779,7 @@ export function InstanceConfigAdminController(app: HonoApp) {
|
||||
const decision = ctx.req.valid('json').status === 'approved' ? 'approve' : 'reject';
|
||||
await updatePendingRegistrationUser(ctx, userId, decision);
|
||||
await instanceConfigRepository.removePendingRegistration(userId);
|
||||
return ctx.json(await buildInstanceConfigResponse());
|
||||
return ctx.json({pending_registrations: await instanceConfigRepository.getPendingRegistrations()});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ import {
|
||||
RequireEmailAccountIdentity,
|
||||
RequireUsernameAccountIdentity,
|
||||
} from '@app/api/middleware/AccountIdentityMiddleware';
|
||||
import {requireAdminACL, requireGrantedAdminACLs} from '@app/api/middleware/AdminMiddleware';
|
||||
import {hasAdminACL, requireAdminACL, requireGrantedAdminACLs} from '@app/api/middleware/AdminMiddleware';
|
||||
import {RateLimitMiddleware} from '@app/api/middleware/RateLimitMiddleware';
|
||||
import {OpenAPI} from '@app/api/middleware/ResponseTypeMiddleware';
|
||||
import {getInstanceConfigRepository} from '@app/api/middleware/ServiceSingletons';
|
||||
import {RateLimitConfigs} from '@app/api/RateLimitConfig';
|
||||
import type {HonoApp} from '@app/api/types/HonoEnv';
|
||||
import {inputValidationErrorFromZodIssues, Validator} from '@app/api/Validator';
|
||||
@@ -94,14 +95,15 @@ export function UserAdminController(app: HonoApp) {
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
description:
|
||||
'Return the admin the request was authenticated as, with the admin permissions, roles, and metadata of the account. Requires AUTHENTICATE permission.',
|
||||
'Return the admin the request was authenticated as, with the admin permissions, roles, and metadata of the account, plus the number of pending registrations when the admin holds REGISTRATION_REVIEW. Requires AUTHENTICATE permission.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminUser = ctx.get('user');
|
||||
const cacheService = ctx.get('cacheService');
|
||||
return ctx.json({
|
||||
user: await mapUserToAdminResponse(adminUser, cacheService),
|
||||
});
|
||||
const canReviewRegistrations = hasAdminACL(ctx.get('adminUserAcls'), AdminACLs.REGISTRATION_REVIEW);
|
||||
const [user, pendingRegistrations] = await Promise.all([
|
||||
mapUserToAdminResponse(ctx.get('user'), ctx.get('cacheService')),
|
||||
canReviewRegistrations ? getInstanceConfigRepository().getPendingRegistrations() : null,
|
||||
]);
|
||||
return ctx.json({user, pending_registration_count: pendingRegistrations?.length ?? null});
|
||||
},
|
||||
);
|
||||
app.get(
|
||||
|
||||
@@ -13,7 +13,9 @@ import {createApiTestHarness} from '@app/api/test/ApiTestHarness';
|
||||
import {HTTP_STATUS} from '@app/api/test/TestConstants';
|
||||
import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequestBuilder';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {afterAll, beforeAll, beforeEach, describe, it} from 'vitest';
|
||||
import type {PendingRegistrationListResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import type {AdminUsersMeResponse} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
|
||||
import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
interface PendingRegistrationResponse {
|
||||
user_id: string;
|
||||
@@ -37,7 +39,7 @@ describe('pending registration approval and the stock community', () => {
|
||||
it('keeps a banned user out of the stock community on approval', async () => {
|
||||
const admin = await setUserACLs(harness, await createTestAccount(harness), [
|
||||
AdminACLs.AUTHENTICATE,
|
||||
AdminACLs.INSTANCE_CONFIG_UPDATE,
|
||||
AdminACLs.REGISTRATION_REVIEW,
|
||||
]);
|
||||
const {owner, guild} = await setupTestGuildWithMembers(harness, 0);
|
||||
await getInstanceConfigRepository().setInstancePolicyConfig({
|
||||
@@ -79,7 +81,7 @@ describe('pending registration approval and the stock community', () => {
|
||||
it('does not add a user who was never pending to the stock community', async () => {
|
||||
const admin = await setUserACLs(harness, await createTestAccount(harness), [
|
||||
AdminACLs.AUTHENTICATE,
|
||||
AdminACLs.INSTANCE_CONFIG_UPDATE,
|
||||
AdminACLs.REGISTRATION_REVIEW,
|
||||
]);
|
||||
const {owner, guild} = await setupTestGuildWithMembers(harness, 0);
|
||||
const outsider = await createTestAccount(harness);
|
||||
@@ -99,4 +101,59 @@ describe('pending registration approval and the stock community', () => {
|
||||
.expect(HTTP_STATUS.NOT_FOUND)
|
||||
.execute();
|
||||
});
|
||||
|
||||
it('lets the review ACL alone list and decide registrations', async () => {
|
||||
const reviewer = await setUserACLs(harness, await createTestAccount(harness), [
|
||||
AdminACLs.AUTHENTICATE,
|
||||
AdminACLs.REGISTRATION_REVIEW,
|
||||
]);
|
||||
const configAdmin = await setUserACLs(harness, await createTestAccount(harness), [
|
||||
AdminACLs.AUTHENTICATE,
|
||||
AdminACLs.INSTANCE_CONFIG_VIEW,
|
||||
AdminACLs.INSTANCE_CONFIG_UPDATE,
|
||||
]);
|
||||
await getInstanceConfigRepository().setRegistrationConfig({mode: 'approval'});
|
||||
const pending = await createBuilderWithoutAuth<PendingRegistrationResponse>(harness)
|
||||
.post('/auth/register')
|
||||
.body({
|
||||
email: createUniqueEmail('reviewonly'),
|
||||
username: createUniqueUsername('reviewonly'),
|
||||
global_name: 'Review only',
|
||||
password: 'reviewing-since-1999',
|
||||
date_of_birth: '2000-01-01',
|
||||
consent: true,
|
||||
})
|
||||
.execute();
|
||||
|
||||
const listed = await createBuilder<PendingRegistrationListResponse>(harness, reviewer.token)
|
||||
.get('/admin/instance/pending-registrations')
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(listed.pending_registrations.map((entry) => entry.user_id)).toEqual([pending.user_id]);
|
||||
const reviewerMe = await createBuilder<AdminUsersMeResponse>(harness, reviewer.token)
|
||||
.get('/admin/users/@me')
|
||||
.execute();
|
||||
expect(reviewerMe.pending_registration_count).toBe(1);
|
||||
const configAdminMe = await createBuilder<AdminUsersMeResponse>(harness, configAdmin.token)
|
||||
.get('/admin/users/@me')
|
||||
.execute();
|
||||
expect(configAdminMe.pending_registration_count).toBeNull();
|
||||
|
||||
await createBuilder(harness, configAdmin.token)
|
||||
.get('/admin/instance/pending-registrations')
|
||||
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_ACL')
|
||||
.execute();
|
||||
await createBuilder(harness, configAdmin.token)
|
||||
.patch(`/admin/instance/pending-registrations/${pending.user_id}`)
|
||||
.body({status: 'approved'})
|
||||
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_ACL')
|
||||
.execute();
|
||||
|
||||
const decided = await createBuilder<PendingRegistrationListResponse>(harness, reviewer.token)
|
||||
.patch(`/admin/instance/pending-registrations/${pending.user_id}`)
|
||||
.body({status: 'rejected'})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(decided.pending_registrations).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -35,7 +35,7 @@ export const InstanceConfigAdminAuditCases: ReadonlyArray<AdminAuditCoverageCase
|
||||
action: 'get_instance_config',
|
||||
targetType: 'instance_config',
|
||||
targetId: '0',
|
||||
metadata: {registration_url_count: '1', pending_registration_count: '0'},
|
||||
metadata: {registration_url_count: '1'},
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -129,6 +129,30 @@ export const InstanceConfigAdminAuditCases: ReadonlyArray<AdminAuditCoverageCase
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
route: '/admin/instance/pending-registrations',
|
||||
async prepare({harness}) {
|
||||
await getInstanceConfigRepository().setRegistrationConfig({mode: 'approval'});
|
||||
await registerUser(harness, {
|
||||
email: createUniqueEmail('coverage-listed'),
|
||||
username: createUniqueUsername('coveragelisted'),
|
||||
global_name: 'Coverage Listed',
|
||||
password: 'coverage-listed-password',
|
||||
date_of_birth: '2000-01-01',
|
||||
consent: true,
|
||||
});
|
||||
return {
|
||||
request: {path: '/admin/instance/pending-registrations'},
|
||||
expected: {
|
||||
action: 'list_pending_registrations',
|
||||
targetType: 'instance_config',
|
||||
targetId: '0',
|
||||
metadata: {pending_registration_count: '1'},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
route: '/admin/instance/pending-registrations/:user_id',
|
||||
|
||||
@@ -23,7 +23,10 @@ import {createBuilder, createBuilderWithoutAuth} from '@app/api/test/TestRequest
|
||||
import {UserRepository} from '@app/api/user/repositories/UserRepository';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import type {InstanceConfigResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import type {
|
||||
InstanceConfigResponse,
|
||||
PendingRegistrationListResponse,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||
|
||||
const REGISTRATION_URLS_KEY = 'registration_urls';
|
||||
@@ -62,6 +65,7 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
AdminACLs.AUTHENTICATE,
|
||||
AdminACLs.INSTANCE_CONFIG_VIEW,
|
||||
AdminACLs.INSTANCE_CONFIG_UPDATE,
|
||||
AdminACLs.REGISTRATION_REVIEW,
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -82,6 +86,13 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
const readAdminConfig = (): Promise<InstanceConfigResponse> =>
|
||||
createBuilder<InstanceConfigResponse>(harness, admin.token).get('/admin/instance/config').execute();
|
||||
|
||||
const readPendingRegistrations = async (): Promise<PendingRegistrationListResponse['pending_registrations']> =>
|
||||
(
|
||||
await createBuilder<PendingRegistrationListResponse>(harness, admin.token)
|
||||
.get('/admin/instance/pending-registrations')
|
||||
.execute()
|
||||
).pending_registrations;
|
||||
|
||||
const completeSso = async (prefix: string) => {
|
||||
const start = await createBuilderWithoutAuth<{state: string}>(harness)
|
||||
.post('/auth/sso/start')
|
||||
@@ -116,7 +127,7 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
};
|
||||
|
||||
const expectOnePendingAccount = async () => {
|
||||
const pending = (await readAdminConfig()).registration.pending_registrations;
|
||||
const pending = await readPendingRegistrations();
|
||||
expect(pending).toHaveLength(1);
|
||||
const account = await new UserRepository().findUnique(createUserID(BigInt(pending[0]!.user_id)));
|
||||
expect(account?.traits.has(REGISTRATION_PENDING_APPROVAL_TRAIT)).toBe(true);
|
||||
@@ -209,8 +220,8 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
|
||||
const failed = await register('seatreleased', code);
|
||||
expect(failed.response.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
||||
expect(await readPendingRegistrations()).toEqual([]);
|
||||
const withdrawn = await readAdminConfig();
|
||||
expect(withdrawn.registration.pending_registrations).toEqual([]);
|
||||
expect(withdrawn.registration.urls.find((url) => url.id === registrationUrl.id)?.use_count).toBe(0);
|
||||
|
||||
const retried = await register('seatreleasedretry', code);
|
||||
@@ -307,7 +318,7 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
const attempts = await Promise.all(Array.from({length: 5}, (_, index) => register(`pending${index}`)));
|
||||
|
||||
expect(attempts.map((attempt) => attempt.json.registration_pending_approval)).toEqual(Array(5).fill(true));
|
||||
const pending = (await readAdminConfig()).registration.pending_registrations.map((entry) => entry.user_id);
|
||||
const pending = (await readPendingRegistrations()).map((entry) => entry.user_id);
|
||||
expect(pending.toSorted()).toEqual(attempts.map((attempt) => attempt.json.user_id).toSorted());
|
||||
});
|
||||
|
||||
@@ -340,7 +351,7 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
const failed = await register('pendingkept');
|
||||
|
||||
expect(failed.response.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
||||
expect((await readAdminConfig()).registration.pending_registrations).toHaveLength(1);
|
||||
expect(await readPendingRegistrations()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('lists no pending approval for an approval-mode signup that failed before the account was created', async () => {
|
||||
@@ -350,7 +361,7 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
const failed = await register('pendingnever');
|
||||
|
||||
expect(failed.response.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
||||
expect((await readAdminConfig()).registration.pending_registrations).toEqual([]);
|
||||
expect(await readPendingRegistrations()).toEqual([]);
|
||||
});
|
||||
|
||||
it('lists an SSO account provisioned in approval mode whose provisioning failed after the account was created', async () => {
|
||||
@@ -383,7 +394,7 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
const failed = await completeSso('ssopendingkept');
|
||||
|
||||
expect(failed.response.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
||||
expect((await readAdminConfig()).registration.pending_registrations).toHaveLength(1);
|
||||
expect(await readPendingRegistrations()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('lists no pending approval for an SSO signup in approval mode that failed before the account was created', async () => {
|
||||
@@ -394,7 +405,7 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
const failed = await completeSso('ssopendingnever');
|
||||
|
||||
expect(failed.response.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
||||
expect((await readAdminConfig()).registration.pending_registrations).toEqual([]);
|
||||
expect(await readPendingRegistrations()).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps a stored pending approval listed until an admin decides it', async () => {
|
||||
@@ -417,16 +428,16 @@ describe('signups racing on registration URLs and pending approvals', () => {
|
||||
await getInstanceConfigRepository().setRegistrationConfig({mode: 'approval'});
|
||||
const fresh = await register('pendingafter');
|
||||
|
||||
const listed = (await readAdminConfig()).registration.pending_registrations.map((entry) => entry.user_id);
|
||||
const listed = (await readPendingRegistrations()).map((entry) => entry.user_id);
|
||||
expect(listed.toSorted()).toEqual([account.userId, fresh.json.user_id].toSorted());
|
||||
|
||||
const decided = await createBuilder<InstanceConfigResponse>(harness, admin.token)
|
||||
const decided = await createBuilder<PendingRegistrationListResponse>(harness, admin.token)
|
||||
.patch(`/admin/instance/pending-registrations/${account.userId}`)
|
||||
.body({status: 'approved'})
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
|
||||
expect(decided.registration.pending_registrations.map((entry) => entry.user_id)).toEqual([fresh.json.user_id]);
|
||||
expect(decided.pending_registrations.map((entry) => entry.user_id)).toEqual([fresh.json.user_id]);
|
||||
expect(
|
||||
JSON.parse(
|
||||
(await getInstanceConfigRepository().getConfig(REGISTRATION_PENDING_APPROVALS_KEY)) ?? 'null',
|
||||
|
||||
@@ -61,7 +61,7 @@ import {
|
||||
InstanceConfigResponse,
|
||||
InstanceConfigUpdateRequest,
|
||||
type InstanceEmailSmtpTestRequest,
|
||||
type PendingRegistrationResponse,
|
||||
PendingRegistrationResponse,
|
||||
type RegistrationUrlResponse,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import {
|
||||
@@ -1053,7 +1053,6 @@ function parseStoredRegistrationConfig(raw: string | null): InstanceRegistration
|
||||
}
|
||||
|
||||
const RegistrationUrlSchema = InstanceConfigResponse.shape.registration.shape.urls.element;
|
||||
const PendingRegistrationSchema = InstanceConfigResponse.shape.registration.shape.pending_registrations.element;
|
||||
const StoredRegistrationTimestampSchema = z.iso
|
||||
.datetime({offset: true})
|
||||
.refine((value) => value === value.trim() && Number.isFinite(Date.parse(value)))
|
||||
@@ -1086,9 +1085,9 @@ const StoredRegistrationUrlSchema = RegistrationUrlSchema.extend({
|
||||
last_used_at: StoredRegistrationTimestampSchema.nullable().default(null),
|
||||
last_used_by_user_id: StoredNullableStringSchema.pipe(StoredSnowflakeStringSchema.nullable()),
|
||||
}) satisfies z.ZodType<InstanceRegistrationUrl>;
|
||||
const StoredPendingRegistrationSchema = PendingRegistrationSchema.extend({
|
||||
const StoredPendingRegistrationSchema = PendingRegistrationResponse.extend({
|
||||
user_id: StoredSnowflakeStringSchema,
|
||||
discriminator: PendingRegistrationSchema.shape.discriminator.default(0),
|
||||
discriminator: PendingRegistrationResponse.shape.discriminator.default(0),
|
||||
global_name: StoredNullableStringSchema,
|
||||
email: StoredNullableStringSchema,
|
||||
requested_at: StoredRegistrationTimestampSchema,
|
||||
|
||||
Reference in new issue
Block a user