import { APIErrorCode, APIException } from "../model/interfaces.ts"; import { db, isInviteRow } from "../model/db.ts"; import { createInviteToken, verifyInviteToken } from "../lib/jwt.ts"; export async function createInvite(inviterId: string): Promise { const token = await createInviteToken(inviterId); db.prepare( `INSERT INTO invites (token, inviter_id, created_at) VALUES (?, ?, ?);`, ).run(token, inviterId, new Date().toISOString()); return token; } /** * Verifies the JWT signature + expiry and checks the token exists and has not * been used. Returns the inviterId on success; throws APIException otherwise. */ export async function validateInvite(token: string): Promise { const payload = await verifyInviteToken(token); if (!payload) { throw new APIException( APIErrorCode.NOT_FOUND, 404, "Invalid or expired invite", ); } const row = db.prepare( `SELECT token, inviter_id, used_at, created_at FROM invites WHERE token = ?;`, ).get(token); if (!row || !isInviteRow(row)) { throw new APIException(APIErrorCode.NOT_FOUND, 404, "Invite not found"); } if (row.used_at !== null) { throw new APIException( APIErrorCode.VALIDATION_ERROR, 409, "Invite already used", ); } return payload.inviterId; } /** * Marks the token as used. Call this only after the user has been created. */ export function redeemInvite(token: string): void { db.prepare( `UPDATE invites SET used_at = ? WHERE token = ?;`, ).run(new Date().toISOString(), token); }