import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; // --------------------------------------------------------------------------- // drinkkaart — one row per user account (1:1 with user) // --------------------------------------------------------------------------- export const drinkkaart = sqliteTable("drinkkaart", { id: text("id").primaryKey(), userId: text("user_id").notNull().unique(), balance: integer("balance").notNull().default(0), // in cents version: integer("version").notNull().default(0), // optimistic lock counter qrSecret: text("qr_secret").notNull(), // CSPRNG 32-byte hex; signs QR tokens createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(), }); // --------------------------------------------------------------------------- // drinkkaart_transaction — immutable audit log of deductions and reversals // --------------------------------------------------------------------------- export const drinkkaartTransaction = sqliteTable("drinkkaart_transaction", { id: text("id").primaryKey(), drinkkaartId: text("drinkkaart_id").notNull(), userId: text("user_id").notNull(), // denormalized for fast audit queries adminId: text("admin_id").notNull(), // FK → user.id (admin who processed it) amountCents: integer("amount_cents").notNull(), balanceBefore: integer("balance_before").notNull(), balanceAfter: integer("balance_after").notNull(), type: text("type", { enum: ["deduction", "reversal"] }).notNull(), reversedBy: text("reversed_by"), // nullable; id of the reversal transaction reverses: text("reverses"), // nullable; id of the original deduction note: text("note"), createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), }); // --------------------------------------------------------------------------- // drinkkaart_topup — immutable log of every credited amount // --------------------------------------------------------------------------- export const drinkkaartTopup = sqliteTable("drinkkaart_topup", { id: text("id").primaryKey(), drinkkaartId: text("drinkkaart_id").notNull(), userId: text("user_id").notNull(), // denormalized amountCents: integer("amount_cents").notNull(), balanceBefore: integer("balance_before").notNull(), balanceAfter: integer("balance_after").notNull(), type: text("type", { enum: ["payment", "admin_credit"] }).notNull(), molliePaymentId: text("mollie_payment_id").unique(), // nullable; only for type="payment" adminId: text("admin_id"), // nullable; only for type="admin_credit" reason: text("reason"), paidAt: integer("paid_at", { mode: "timestamp_ms" }).notNull(), }); // --------------------------------------------------------------------------- // Inferred Drizzle types // --------------------------------------------------------------------------- export type Drinkkaart = InferSelectModel; export type NewDrinkkaart = InferInsertModel; export type DrinkkaartTransaction = InferSelectModel< typeof drinkkaartTransaction >; export type NewDrinkkaartTransaction = InferInsertModel< typeof drinkkaartTransaction >; export type DrinkkaartTopup = InferSelectModel; export type NewDrinkkaartTopup = InferInsertModel;