150 lines
4.0 KiB
TypeScript
150 lines
4.0 KiB
TypeScript
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<T extends keyof CodeRpcArgs>(
|
|
functionName: T,
|
|
session: Pick<AdminSession, "user">,
|
|
id: string,
|
|
args: CodeRpcArgs[T],
|
|
): Promise<RedemptionCodeRecord[]> {
|
|
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<RpcCodeRow>(
|
|
`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<RpcCodeRow>(
|
|
`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<RpcCodeRow>(
|
|
`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);
|
|
}
|