feat:drinkkaart
This commit is contained in:
56
packages/db/src/migrations/0004_drinkkaart.sql
Normal file
56
packages/db/src/migrations/0004_drinkkaart.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Migration: Add Drinkkaart digital balance card tables
|
||||
CREATE TABLE `drinkkaart` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`balance` integer DEFAULT 0 NOT NULL,
|
||||
`version` integer DEFAULT 0 NOT NULL,
|
||||
`qr_secret` text NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
`updated_at` integer NOT NULL,
|
||||
UNIQUE(`user_id`)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `drinkkaart_transaction` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`drinkkaart_id` text NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`admin_id` text NOT NULL,
|
||||
`amount_cents` integer NOT NULL,
|
||||
`balance_before` integer NOT NULL,
|
||||
`balance_after` integer NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`reversed_by` text,
|
||||
`reverses` text,
|
||||
`note` text,
|
||||
`created_at` integer NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `drinkkaart_topup` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`drinkkaart_id` text NOT NULL,
|
||||
`user_id` text NOT NULL,
|
||||
`amount_cents` integer NOT NULL,
|
||||
`balance_before` integer NOT NULL,
|
||||
`balance_after` integer NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`lemonsqueezy_order_id` text,
|
||||
`lemonsqueezy_customer_id` text,
|
||||
`admin_id` text,
|
||||
`reason` text,
|
||||
`paid_at` integer NOT NULL,
|
||||
UNIQUE(`lemonsqueezy_order_id`)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_drinkkaart_user_id` ON `drinkkaart` (`user_id`);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_dkt_drinkkaart_id` ON `drinkkaart_transaction` (`drinkkaart_id`);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_dkt_user_id` ON `drinkkaart_transaction` (`user_id`);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_dkt_admin_id` ON `drinkkaart_transaction` (`admin_id`);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_dkt_created_at` ON `drinkkaart_transaction` (`created_at`);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_dktu_drinkkaart_id` ON `drinkkaart_topup` (`drinkkaart_id`);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `idx_dktu_user_id` ON `drinkkaart_topup` (`user_id`);
|
||||
@@ -22,6 +22,13 @@
|
||||
"when": 1772520000000,
|
||||
"tag": "0002_registration_type_redesign",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "6",
|
||||
"when": 1772530000000,
|
||||
"tag": "0003_add_guests",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
69
packages/db/src/schema/drinkkaart.ts
Normal file
69
packages/db/src/schema/drinkkaart.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
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(),
|
||||
lemonsqueezyOrderId: text("lemonsqueezy_order_id").unique(), // nullable; only for type="payment"
|
||||
lemonsqueezyCustomerId: text("lemonsqueezy_customer_id"),
|
||||
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<typeof drinkkaart>;
|
||||
export type NewDrinkkaart = InferInsertModel<typeof drinkkaart>;
|
||||
export type DrinkkaartTransaction = InferSelectModel<
|
||||
typeof drinkkaartTransaction
|
||||
>;
|
||||
export type NewDrinkkaartTransaction = InferInsertModel<
|
||||
typeof drinkkaartTransaction
|
||||
>;
|
||||
export type DrinkkaartTopup = InferSelectModel<typeof drinkkaartTopup>;
|
||||
export type NewDrinkkaartTopup = InferInsertModel<typeof drinkkaartTopup>;
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./admin-requests";
|
||||
export * from "./auth";
|
||||
export * from "./drinkkaart";
|
||||
export * from "./registrations";
|
||||
|
||||
Reference in New Issue
Block a user