import "server-only"; import { queryAdminRows } from "./database"; import type { AdminSession } from "./auth"; export type RedemptionCodeRecord = { id: string; mask: string; credits: number; expiresAt: string | null; note: string | null; createdAt: string; redeemedBy: string | null; redeemedEmail: string | null; redeemedAt: string | null; revokedBy: string | null; revokedAt: string | null; status: "available" | "expired" | "redeemed" | "revoked"; }; type RpcCodeRow = { id: string; code_mask: string; credits: number; expires_at: Date | string | null; note: string | null; created_at: Date | string; redeemed_by: string | null; redeemed_email: string | null; redeemed_at: Date | string | null; revoked_by: string | null; revoked_at: Date | string | null; }; type CodeRpcArgs = { admin_create_redemption_codes: { p_codes: unknown; p_reason: string; }; admin_update_redemption_code: { p_code_id: string; p_set_note: boolean; p_note: string | null; p_set_expires_at: boolean; p_expires_at: string | null; p_reason: string; }; admin_revoke_redemption_code: { p_code_id: string; p_reason: string; }; }; function isoTimestamp(value: Date | string | null): string | null { return value instanceof Date ? value.toISOString() : value; } export function codeStatus(row: RpcCodeRow): RedemptionCodeRecord["status"] { const expiresAt = isoTimestamp(row.expires_at); if (row.redeemed_at) return "redeemed"; if (row.revoked_at) return "revoked"; if (expiresAt && Date.parse(expiresAt) <= Date.now()) return "expired"; return "available"; } export function mapCode(row: RpcCodeRow): RedemptionCodeRecord { const expiresAt = isoTimestamp(row.expires_at); const redeemedAt = isoTimestamp(row.redeemed_at); const revokedAt = isoTimestamp(row.revoked_at); return { id: row.id, mask: row.code_mask, credits: row.credits, expiresAt, note: row.note, createdAt: isoTimestamp(row.created_at)!, redeemedBy: row.redeemed_by, redeemedEmail: row.redeemed_email, redeemedAt, revokedBy: row.revoked_by, revokedAt, status: codeStatus({ ...row, expires_at: expiresAt, created_at: isoTimestamp(row.created_at)!, redeemed_at: redeemedAt, revoked_at: revokedAt, }), }; } export async function runCodeRpc( functionName: T, session: Pick, id: string, args: CodeRpcArgs[T], ): Promise { const common = [ session.user.id, session.user.email, "admin", id, ]; let rows: RpcCodeRow[] = []; switch (functionName) { case "admin_create_redemption_codes": { const input = args as CodeRpcArgs["admin_create_redemption_codes"]; rows = await queryAdminRows( `select * from public.admin_create_redemption_codes( $1::uuid,$2::text,$3::text,$4::text,$5::jsonb,$6::text )`, [...common, JSON.stringify(input.p_codes), input.p_reason], ); break; } case "admin_update_redemption_code": { const input = args as CodeRpcArgs["admin_update_redemption_code"]; rows = await queryAdminRows( `select * from public.admin_update_redemption_code( $1::uuid,$2::text,$3::text,$4::text,$5::uuid, $6::boolean,$7::text,$8::boolean,$9::timestamptz,$10::text )`, [ ...common, input.p_code_id, input.p_set_note, input.p_note, input.p_set_expires_at, input.p_expires_at, input.p_reason, ], ); break; } case "admin_revoke_redemption_code": { const input = args as CodeRpcArgs["admin_revoke_redemption_code"]; rows = await queryAdminRows( `select * from public.admin_revoke_redemption_code( $1::uuid,$2::text,$3::text,$4::text,$5::uuid,$6::text )`, [...common, input.p_code_id, input.p_reason], ); break; } } return rows.map(mapCode); }