mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-20 01:35:19 +03:00
Squashes the current state of the internal working tree onto this history. The two trees had grown apart with no common ancestor, so this is a content sync rather than a merge, and the published history is preserved rather than rewritten — a force-push here would break every existing fork and clone to no benefit. What lands: - Waitlist: API route, core module, and migration 0006 alongside game aliases. - User verification. - CI, oxfmt config, editor settings. - Assorted fixes across the API routes and core modules. The repository's own README, the wordmark and the per-package READMEs are kept from this side; the internal tree had dropped them and they are what a stranger arriving here reads first. The marketing site in the internal tree is deliberately not here. It is a separate product with its own repo and its own licence, and this repo is the open one — a closed component does not belong in it regardless of how convenient the directory looked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
87 lines
2.2 KiB
TypeScript
87 lines
2.2 KiB
TypeScript
import { Examples } from '@nestri/core/examples';
|
|
import { Waitlist } from '@nestri/core/waitlist/index';
|
|
import { Hono } from 'hono';
|
|
import { describeRoute } from 'hono-openapi';
|
|
import { z } from 'zod';
|
|
|
|
import { adminOnly, ErrorResponses, Result, validator } from '../utils';
|
|
|
|
/**
|
|
* Public signups for not-yet-launched features (the machines waitlist).
|
|
*
|
|
* Deliberately unauthenticated: a visitor without an account should be able
|
|
* to leave an email. The list itself is admin-only so a scraper cannot mine
|
|
* every address out of the response.
|
|
*/
|
|
export namespace WaitlistApi {
|
|
export const route = new Hono()
|
|
.post(
|
|
'/',
|
|
describeRoute({
|
|
tags: ['Waitlist'],
|
|
summary: 'Join the waitlist',
|
|
description: 'Leave an email to be notified when a feature launches. Public.',
|
|
responses: {
|
|
201: {
|
|
content: {
|
|
'application/json': {
|
|
schema: Result(
|
|
Waitlist.Info.meta({
|
|
description: 'The waitlist entry (the existing one if already joined)',
|
|
example: Examples.WaitlistEntry
|
|
})
|
|
)
|
|
}
|
|
},
|
|
description: 'Joined the waitlist'
|
|
},
|
|
400: ErrorResponses[400]
|
|
}
|
|
}),
|
|
validator(
|
|
'json',
|
|
z.object({
|
|
email: z.email().meta({
|
|
description: 'The email to notify',
|
|
example: Examples.WaitlistEntry.email
|
|
}),
|
|
source: z.string().default('machines').meta({
|
|
description: 'What the signup is for',
|
|
example: Examples.WaitlistEntry.source
|
|
})
|
|
})
|
|
),
|
|
async (c) => {
|
|
const { email, source } = c.req.valid('json');
|
|
const entry = await Waitlist.join({ email, source });
|
|
return c.json({ data: entry }, 201);
|
|
}
|
|
)
|
|
.get(
|
|
'/',
|
|
adminOnly,
|
|
describeRoute({
|
|
tags: ['Waitlist'],
|
|
summary: 'List waitlist entries',
|
|
description: 'Every email currently on the waitlist. Admin only.',
|
|
responses: {
|
|
200: {
|
|
content: {
|
|
'application/json': {
|
|
schema: Result(
|
|
z.array(Waitlist.Info).meta({
|
|
description: 'All waitlist entries',
|
|
example: [Examples.WaitlistEntry]
|
|
})
|
|
)
|
|
}
|
|
},
|
|
description: 'Waitlist entries'
|
|
},
|
|
403: ErrorResponses[403]
|
|
}
|
|
}),
|
|
async (c) => c.json({ data: await Waitlist.list() })
|
|
);
|
|
}
|