import { Hono } from 'hono' import * as z from 'zod/mini' import * as Db from '../../db/Db.js' import * as db_Schema from '../../db/Schema.js' import * as EarlyAccess from '../../db/tables/earlyAccess.js' import * as OpenApi from '../../internal/OpenApi.js' import * as Response from '../../internal/Response.js' import * as Schema from '../../internal/Schema.js' import type * as App from '../App.js' // A bare domain (`example.org`) or a full email (`user@example.org`), matched // after the trim/lowercase transforms below. const entryPattern = /^(?:[^\s@]+@)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/ /** Zod schemas owned by the admin early-access handler. */ export namespace schema { /** A stored early-access allowlist entry (the `early_access` row shape). */ export const Entry = Schema.describe( db_Schema.EarlyAccessEntry, 'A stored early-access allowlist entry.', ) /** Schemas for the listEarlyAccessEntries operation. */ export namespace listEarlyAccessEntries { /** Response body: all allowlist entries. */ export const Response = Schema.describe( z.object({ data: z.array(Entry).check(z.describe('Allowlist entries, ordered by entry.')), }), 'Accounts whose new organizations receive early access features by default.', ) } /** Schemas for the createEarlyAccessEntry operation. */ export namespace createEarlyAccessEntry { /** Request body for adding an allowlist entry. */ export const Body = z .object({ entry: z.string().check( z.trim(), z.toLowerCase(), z.regex(entryPattern, 'Must be a bare domain (example.org) or a full email (user@example.org).'), // prettier-ignore z.describe('Domain or exact email whose new organizations receive early access features; normalized lowercase.'), // prettier-ignore ), }) .check(z.describe('Request body for adding an allowlist entry.')) /** Response body: the stored entry. */ export const Response = Schema.describe( z.object({ data: Entry }), 'The stored allowlist entry.', ) } /** Schemas for the deleteEarlyAccessEntry operation. */ export namespace deleteEarlyAccessEntry { /** Request body addressing one allowlist entry. */ export const Body = z .object({ entry: z.string().check( z.trim(), z.toLowerCase(), z.regex(entryPattern, 'Must be a bare domain (example.org) or a full email (user@example.org).'), // prettier-ignore z.describe('The allowlist entry to remove; normalized lowercase.'), ), }) .check(z.describe('Request body for removing an allowlist entry.')) /** Response body: the removed entry. */ export const Response = Schema.describe( z.object({ data: Entry }), 'The removed allowlist entry.', ) } } /** * Management routes for the early-access allowlist (`early_access` table). * Matching users receive Stripe by default when they create an organization. * * - `GET /` lists entries. * - `POST /` adds an entry (`409` on a duplicate). * - `DELETE /` removes an entry (`404` if absent). */ export function earlyAccess() { return new Hono() .get( '/', OpenApi.describeRoute({ operationId: 'listEarlyAccessEntries', responses: OpenApi.responses({ success: { description: 'Accounts whose new organizations receive early access features by default.', schema: schema.listEarlyAccessEntries.Response, }, }), summary: 'List early access', tags: ['Early Access'], }), async (c) => { const db = Db.get(c.get('db')) return c.json( Response.validated(schema.listEarlyAccessEntries.Response, { data: await EarlyAccess.list(db), }), ) }, ) .post( '/', OpenApi.validate('json', schema.createEarlyAccessEntry.Body, { code: 'body_invalid', message: 'Invalid request body', }), OpenApi.describeRoute({ operationId: 'createEarlyAccessEntry', responses: OpenApi.responses({ errors: { 409: 'Entry already exists.' }, success: { description: 'The stored allowlist entry.', schema: schema.createEarlyAccessEntry.Response, }, }), summary: 'Add early-access entry', tags: ['Early Access'], }), async (c) => { const db = Db.get(c.get('db')) const { entry } = c.req.valid('json') // `createdBy` is the verified admin email; `requireAuth` guarantees // identity on this gated route. const record = await EarlyAccess.add(db, { createdBy: c.get('identity')!.email, entry }) if (!record) return Response.error(c, { code: 'entry_exists', message: 'Entry already exists.', status: 409, }) return c.json(Response.validated(schema.createEarlyAccessEntry.Response, { data: record })) }, ) .delete( '/', OpenApi.validate('json', schema.deleteEarlyAccessEntry.Body, { code: 'body_invalid', message: 'Invalid request body', }), OpenApi.describeRoute({ operationId: 'deleteEarlyAccessEntry', responses: OpenApi.responses({ errors: { 404: 'Entry not found.' }, success: { description: 'The removed allowlist entry.', schema: schema.deleteEarlyAccessEntry.Response, }, }), summary: 'Remove early-access entry', tags: ['Early Access'], }), async (c) => { const db = Db.get(c.get('db')) const { entry } = c.req.valid('json') const record = await EarlyAccess.remove(db, entry) if (!record) return Response.error(c, { code: 'entry_not_found', message: 'Entry not found.', status: 404, }) return c.json(Response.validated(schema.deleteEarlyAccessEntry.Response, { data: record })) }, ) }