import { all, batch, first, run, stmt } from "./db";
import { randomToken, sha256Hex } from "./crypto";
import { emailTemplates, queueEmail } from "./email";
import { badRequest } from "./errors";
import { publicUrl } from "./events/emit";
import { WAITLIST_INVITE_HOURS } from "~/lib/waitlist";
const CAP_KEY = "signup_cap";
const INVITE_TTL_MS = WAITLIST_INVITE_HOURS * 60 * 60 * 1000;
/** Invites sent per advance; a cron or the next cap change picks up the rest. */
const INVITES_PER_RUN = 100;
export const MAX_SIGNUP_CAP = 1_000_000;
/** Seats held by people invited off the waitlist whose invite hasn't lapsed. */
const HELD_SEATS = "(SELECT COUNT(*) FROM signup_waitlist WHERE invite_expires_at > ? AND bypass_cap = 0)";
const USERS = "(SELECT COUNT(*) FROM users)";
/** The admin-set cap on user accounts, or null for no cap. */
export async function signupCap(): Promise<number | null> {
const row = await first<{ value: string }>("SELECT value FROM instance_settings WHERE key = ?", CAP_KEY);
const cap = row ? Number(row.value) : Number.NaN;
return Number.isInteger(cap) && cap >= 0 ? cap : null;
}
export async function setSignupCap(cap: number | null): Promise<void> {
if (cap === null) {
await run("DELETE FROM instance_settings WHERE key = ?", CAP_KEY);
} else {
await run(
`INSERT INTO instance_settings (key, value, updated_at) VALUES (?, ?, ?)
ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,
CAP_KEY,
String(cap),
Date.now(),
);
}
await advanceWaitlist();
}
export type SignupSeat =