diff --git a/apps/web/src/components/registration/GuestList.tsx b/apps/web/src/components/registration/GuestList.tsx index 1dd0a32..d7af695 100644 --- a/apps/web/src/components/registration/GuestList.tsx +++ b/apps/web/src/components/registration/GuestList.tsx @@ -245,6 +245,169 @@ export function GuestList({ )} + + {/* Birthdate */} +
+

+ Geboortedatum * +

+
+ + Geboortedatum medebezoeker {idx + 1} + +
+ + +
+
+ + +
+
+ + +
+
+ {errors[idx]?.birthdate && ( + + {errors[idx].birthdate} + + )} +
+ + {/* Postcode */} +
+ + onChange(idx, "postcode", e.target.value)} + placeholder="bv. 9000" + autoComplete="off" + inputMode="numeric" + className={inputCls(!!errors[idx]?.postcode)} + /> + {errors[idx]?.postcode && ( + + {errors[idx].postcode} + + )} +
))} diff --git a/apps/web/src/components/registration/PerformerForm.tsx b/apps/web/src/components/registration/PerformerForm.tsx index 4f09e39..7fa6a93 100644 --- a/apps/web/src/components/registration/PerformerForm.tsx +++ b/apps/web/src/components/registration/PerformerForm.tsx @@ -4,8 +4,10 @@ import { toast } from "sonner"; import { authClient } from "@/lib/auth-client"; import { inputCls, + validateBirthdate, validateEmail, validatePhone, + validatePostcode, validateTextField, } from "@/lib/registration"; import { orpc } from "@/utils/orpc"; @@ -16,6 +18,8 @@ interface PerformerErrors { lastName?: string; email?: string; phone?: string; + birthdate?: string; + postcode?: string; artForm?: string; isOver16?: string; } @@ -34,6 +38,10 @@ export function PerformerForm({ onBack, onSuccess }: Props) { lastName: "", email: "", phone: "", + birthdateDay: "", + birthdateMonth: "", + birthdateYear: "", + postcode: "", artForm: "", experience: "", isOver16: false, @@ -64,12 +72,21 @@ export function PerformerForm({ onBack, onSuccess }: Props) { }, }); + function getBirthdate(): string { + const { birthdateYear, birthdateMonth, birthdateDay } = data; + if (!birthdateYear || !birthdateMonth || !birthdateDay) return ""; + return `${birthdateYear}-${birthdateMonth.padStart(2, "0")}-${birthdateDay.padStart(2, "0")}`; + } + function validate(): boolean { + const birthdate = getBirthdate(); const errs: PerformerErrors = { firstName: validateTextField(data.firstName, true, "Voornaam"), lastName: validateTextField(data.lastName, true, "Achternaam"), email: validateEmail(data.email), phone: validatePhone(data.phone), + birthdate: validateBirthdate(birthdate), + postcode: validatePostcode(data.postcode), artForm: !data.artForm.trim() ? "Kunstvorm is verplicht" : undefined, isOver16: !data.isOver16 ? "Je moet 16 jaar of ouder zijn om op te treden" @@ -81,6 +98,8 @@ export function PerformerForm({ onBack, onSuccess }: Props) { lastName: true, email: true, phone: true, + birthdate: true, + postcode: true, artForm: true, isOver16: true, }); @@ -144,6 +163,8 @@ export function PerformerForm({ onBack, onSuccess }: Props) { lastName: data.lastName.trim(), email: data.email.trim(), phone: data.phone.trim() || undefined, + birthdate: getBirthdate(), + postcode: data.postcode.trim(), registrationType: "performer", artForm: data.artForm.trim() || undefined, experience: data.experience.trim() || undefined, @@ -305,6 +326,149 @@ export function PerformerForm({ onBack, onSuccess }: Props) { + {/* Birthdate + Postcode row */} +
+
+

+ Geboortedatum * +

+
+ Geboortedatum +
+ + +
+
+ + +
+
+ + +
+
+ {touched.birthdate && errors.birthdate && ( + + {errors.birthdate} + + )} +
+ +
+ + + {touched.postcode && errors.postcode && ( + + {errors.postcode} + + )} +
+
+ {/* Performer-specific fields */}

diff --git a/apps/web/src/components/registration/WatcherForm.tsx b/apps/web/src/components/registration/WatcherForm.tsx index 67d757e..854346e 100644 --- a/apps/web/src/components/registration/WatcherForm.tsx +++ b/apps/web/src/components/registration/WatcherForm.tsx @@ -7,9 +7,11 @@ import { type GuestEntry, type GuestErrors, inputCls, + validateBirthdate, validateEmail, validateGuests, validatePhone, + validatePostcode, validateTextField, } from "@/lib/registration"; import { orpc } from "@/utils/orpc"; @@ -21,6 +23,8 @@ interface WatcherErrors { lastName?: string; email?: string; phone?: string; + birthdate?: string; + postcode?: string; } interface Props { @@ -39,6 +43,10 @@ export function WatcherForm({ onBack, onSuccess }: Props) { lastName: "", email: "", phone: "", + birthdateDay: "", + birthdateMonth: "", + birthdateYear: "", + postcode: "", extraQuestions: "", }); const [errors, setErrors] = useState({}); @@ -69,12 +77,21 @@ export function WatcherForm({ onBack, onSuccess }: Props) { }, }); + function getBirthdate(): string { + const { birthdateYear, birthdateMonth, birthdateDay } = data; + if (!birthdateYear || !birthdateMonth || !birthdateDay) return ""; + return `${birthdateYear}-${birthdateMonth.padStart(2, "0")}-${birthdateDay.padStart(2, "0")}`; + } + function validate(): boolean { + const birthdate = getBirthdate(); const fieldErrs: WatcherErrors = { firstName: validateTextField(data.firstName, true, "Voornaam"), lastName: validateTextField(data.lastName, true, "Achternaam"), email: validateEmail(data.email), phone: validatePhone(data.phone), + birthdate: validateBirthdate(birthdate), + postcode: validatePostcode(data.postcode), }; setErrors(fieldErrs); setTouched({ @@ -82,6 +99,8 @@ export function WatcherForm({ onBack, onSuccess }: Props) { lastName: true, email: true, phone: true, + birthdate: true, + postcode: true, }); const { errors: gErrs, valid: gValid } = validateGuests(guests); setGuestErrors(gErrs); @@ -134,7 +153,14 @@ export function WatcherForm({ onBack, onSuccess }: Props) { if (guests.length >= 9) return; setGuests((prev) => [ ...prev, - { firstName: "", lastName: "", email: "", phone: "" }, + { + firstName: "", + lastName: "", + email: "", + phone: "", + birthdate: "", + postcode: "", + }, ]); setGuestErrors((prev) => [...prev, {}]); } @@ -155,12 +181,16 @@ export function WatcherForm({ onBack, onSuccess }: Props) { lastName: data.lastName.trim(), email: data.email.trim(), phone: data.phone.trim() || undefined, + birthdate: getBirthdate(), + postcode: data.postcode.trim(), registrationType: "watcher", guests: guests.map((g) => ({ firstName: g.firstName.trim(), lastName: g.lastName.trim(), email: g.email.trim() || undefined, phone: g.phone.trim() || undefined, + birthdate: g.birthdate.trim(), + postcode: g.postcode.trim(), })), extraQuestions: data.extraQuestions.trim() || undefined, giftAmount, @@ -341,6 +371,146 @@ export function WatcherForm({ onBack, onSuccess }: Props) {

+ {/* Birthdate + Postcode row */} +
+
+

+ Geboortedatum * +

+
+ Geboortedatum +
+ + +
+
+ + +
+
+ + +
+
+ {touched.birthdate && errors.birthdate && ( + + {errors.birthdate} + + )} +
+ +
+ + + {touched.postcode && errors.postcode && ( + + {errors.postcode} + + )} +
+
+ {/* Guests */} new Date()) return "Geboortedatum mag niet in de toekomst liggen"; + return undefined; +} + +export function validatePostcode(value: string): string | undefined { + if (!value.trim()) return "Postcode is verplicht"; + return undefined; +} + /** Validates all guests and returns errors array + overall validity flag. */ export function validateGuests(guests: GuestEntry[]): { errors: GuestErrors[]; @@ -103,6 +125,8 @@ export function validateGuests(guests: GuestEntry[]): { g.phone.trim() && !/^[\d\s\-+()]{10,}$/.test(g.phone.replace(/\s/g, "")) ? "Voer een geldig telefoonnummer in" : undefined, + birthdate: validateBirthdate(g.birthdate), + postcode: validatePostcode(g.postcode), })); const valid = !errors.some((e) => Object.values(e).some(Boolean)); return { errors, valid }; diff --git a/apps/web/src/routes/manage.$token.tsx b/apps/web/src/routes/manage.$token.tsx index 3016fbc..f15fb8e 100644 --- a/apps/web/src/routes/manage.$token.tsx +++ b/apps/web/src/routes/manage.$token.tsx @@ -117,6 +117,8 @@ interface EditFormProps { lastName: string; email: string; phone: string | null; + birthdate: string; + postcode: string; registrationType: string; artForm: string | null; experience: string | null; @@ -140,6 +142,8 @@ function EditForm({ token, initialData, onCancel, onSaved }: EditFormProps) { lastName: initialData.lastName, email: initialData.email, phone: initialData.phone ?? "", + birthdate: initialData.birthdate ?? "", + postcode: initialData.postcode ?? "", registrationType: initialType, artForm: initialData.artForm ?? "", experience: initialData.experience ?? "", @@ -185,6 +189,8 @@ function EditForm({ token, initialData, onCancel, onSaved }: EditFormProps) { ? formData.experience.trim() || undefined : undefined, isOver16: performer ? formData.isOver16 : false, + birthdate: formData.birthdate.trim(), + postcode: formData.postcode.trim(), guests: performer ? [] : formGuests.map((g) => ({ @@ -192,6 +198,8 @@ function EditForm({ token, initialData, onCancel, onSaved }: EditFormProps) { lastName: g.lastName.trim(), email: g.email.trim() || undefined, phone: g.phone.trim() || undefined, + birthdate: g.birthdate.trim(), + postcode: g.postcode.trim(), })), extraQuestions: formData.extraQuestions.trim() || undefined, giftAmount, @@ -393,7 +401,14 @@ function EditForm({ token, initialData, onCancel, onSaved }: EditFormProps) { if (formGuests.length >= 9) return; setFormGuests((prev) => [ ...prev, - { firstName: "", lastName: "", email: "", phone: "" }, + { + firstName: "", + lastName: "", + email: "", + phone: "", + birthdate: "", + postcode: "", + }, ]); }} onRemove={(idx) => diff --git a/apps/web/src/routes/privacy.tsx b/apps/web/src/routes/privacy.tsx index a0be529..61135d0 100644 --- a/apps/web/src/routes/privacy.tsx +++ b/apps/web/src/routes/privacy.tsx @@ -26,7 +26,7 @@ function PrivacyPage() {

We verzamelen alleen de gegevens die je zelf invoert bij de registratie: voornaam, achternaam, e-mailadres, telefoonnummer, - kunstvorm en ervaring. + geboortedatum, postcode, kunstvorm en ervaring.

@@ -39,6 +39,12 @@ function PrivacyPage() { Mic Night, om het programma samen te stellen en om je te informeren over aanvullende details over het evenement.

+

+ Geboortedatum en postcode{" "} + worden gevraagd ter naleving van de rapportageverplichtingen van{" "} + EJV en{" "} + de Vlaamse Overheid. +

diff --git a/packages/api/src/routers/index.ts b/packages/api/src/routers/index.ts index c0366ab..4383f76 100644 --- a/packages/api/src/routers/index.ts +++ b/packages/api/src/routers/index.ts @@ -262,6 +262,8 @@ const guestSchema = z.object({ lastName: z.string().min(1), email: z.string().email().optional().or(z.literal("")), phone: z.string().optional(), + birthdate: z.string().min(1), + postcode: z.string().min(1), }); const coreRegistrationFields = { @@ -269,6 +271,8 @@ const coreRegistrationFields = { lastName: z.string().min(1), email: z.string().email(), phone: z.string().optional(), + birthdate: z.string().min(1), + postcode: z.string().min(1), registrationType: registrationTypeSchema.default("watcher"), artForm: z.string().optional(), experience: z.string().optional(), @@ -366,6 +370,8 @@ export const appRouter = { lastName: input.lastName, email: input.email, phone: input.phone || null, + birthdate: input.birthdate, + postcode: input.postcode, registrationType: input.registrationType, artForm: isPerformer ? input.artForm || null : null, experience: isPerformer ? input.experience || null : null, @@ -463,6 +469,8 @@ export const appRouter = { lastName: input.lastName, email: input.email, phone: input.phone || null, + birthdate: input.birthdate, + postcode: input.postcode, registrationType: input.registrationType, artForm: isPerformer ? input.artForm || null : null, experience: isPerformer ? input.experience || null : null, @@ -662,6 +670,8 @@ export const appRouter = { "Last Name", "Email", "Phone", + "Birthdate", + "Postcode", "Type", "Art Form", "Experience", @@ -673,38 +683,56 @@ export const appRouter = { "Guest 1 Last Name", "Guest 1 Email", "Guest 1 Phone", + "Guest 1 Birthdate", + "Guest 1 Postcode", "Guest 2 First Name", "Guest 2 Last Name", "Guest 2 Email", "Guest 2 Phone", + "Guest 2 Birthdate", + "Guest 2 Postcode", "Guest 3 First Name", "Guest 3 Last Name", "Guest 3 Email", "Guest 3 Phone", + "Guest 3 Birthdate", + "Guest 3 Postcode", "Guest 4 First Name", "Guest 4 Last Name", "Guest 4 Email", "Guest 4 Phone", + "Guest 4 Birthdate", + "Guest 4 Postcode", "Guest 5 First Name", "Guest 5 Last Name", "Guest 5 Email", "Guest 5 Phone", + "Guest 5 Birthdate", + "Guest 5 Postcode", "Guest 6 First Name", "Guest 6 Last Name", "Guest 6 Email", "Guest 6 Phone", + "Guest 6 Birthdate", + "Guest 6 Postcode", "Guest 7 First Name", "Guest 7 Last Name", "Guest 7 Email", "Guest 7 Phone", + "Guest 7 Birthdate", + "Guest 7 Postcode", "Guest 8 First Name", "Guest 8 Last Name", "Guest 8 Email", "Guest 8 Phone", + "Guest 8 Birthdate", + "Guest 8 Postcode", "Guest 9 First Name", "Guest 9 Last Name", "Guest 9 Email", "Guest 9 Phone", + "Guest 9 Birthdate", + "Guest 9 Postcode", "Payment Status", "Paid At", "Extra Questions", @@ -717,7 +745,7 @@ export const appRouter = { const rows = data.map((r) => { const guests = parseGuestsJson(r.guests); - // Build guest columns (up to 9 guests, 4 fields each) + // Build guest columns (up to 9 guests, 6 fields each) const guestCols: string[] = []; for (let i = 0; i < MAX_GUESTS; i++) { const g = guests[i]; @@ -725,6 +753,8 @@ export const appRouter = { guestCols.push(g?.lastName ?? ""); guestCols.push(g?.email ?? ""); guestCols.push(g?.phone ?? ""); + guestCols.push((g as { birthdate?: string })?.birthdate ?? ""); + guestCols.push((g as { postcode?: string })?.postcode ?? ""); } return [ @@ -733,6 +763,8 @@ export const appRouter = { r.lastName, r.email, r.phone || "", + r.birthdate || "", + r.postcode || "", r.registrationType ?? "", r.artForm || "", r.experience || "", diff --git a/packages/db/src/migrations/0008_melodic_marrow.sql b/packages/db/src/migrations/0008_melodic_marrow.sql new file mode 100644 index 0000000..d48946e --- /dev/null +++ b/packages/db/src/migrations/0008_melodic_marrow.sql @@ -0,0 +1,101 @@ +ALTER TABLE `registration` RENAME COLUMN "wants_to_perform" TO "registration_type";--> statement-breakpoint +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 +); +--> statement-breakpoint +CREATE UNIQUE INDEX `drinkkaart_user_id_unique` ON `drinkkaart` (`user_id`);--> 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, + `mollie_payment_id` text, + `admin_id` text, + `reason` text, + `paid_at` integer NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `drinkkaart_topup_mollie_payment_id_unique` ON `drinkkaart_topup` (`mollie_payment_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 `reminder` ( + `id` text PRIMARY KEY NOT NULL, + `email` text NOT NULL, + `sent_24h_at` integer, + `sent_at` integer, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL +); +--> statement-breakpoint +CREATE INDEX `reminder_email_idx` ON `reminder` (`email`);--> statement-breakpoint +DROP INDEX "admin_request_user_id_unique";--> statement-breakpoint +DROP INDEX "admin_request_userId_idx";--> statement-breakpoint +DROP INDEX "admin_request_status_idx";--> statement-breakpoint +DROP INDEX "account_userId_idx";--> statement-breakpoint +DROP INDEX "session_token_unique";--> statement-breakpoint +DROP INDEX "session_userId_idx";--> statement-breakpoint +DROP INDEX "user_email_unique";--> statement-breakpoint +DROP INDEX "verification_identifier_idx";--> statement-breakpoint +DROP INDEX "drinkkaart_user_id_unique";--> statement-breakpoint +DROP INDEX "drinkkaart_topup_mollie_payment_id_unique";--> statement-breakpoint +DROP INDEX "registration_management_token_unique";--> statement-breakpoint +DROP INDEX "registration_email_idx";--> statement-breakpoint +DROP INDEX "registration_registrationType_idx";--> statement-breakpoint +DROP INDEX "registration_artForm_idx";--> statement-breakpoint +DROP INDEX "registration_createdAt_idx";--> statement-breakpoint +DROP INDEX "registration_managementToken_idx";--> statement-breakpoint +DROP INDEX "registration_paymentStatus_idx";--> statement-breakpoint +DROP INDEX "registration_giftAmount_idx";--> statement-breakpoint +DROP INDEX "registration_molliePaymentId_idx";--> statement-breakpoint +DROP INDEX "reminder_email_idx";--> statement-breakpoint +ALTER TABLE `registration` ALTER COLUMN "registration_type" TO "registration_type" text NOT NULL DEFAULT 'watcher';--> statement-breakpoint +CREATE UNIQUE INDEX `admin_request_user_id_unique` ON `admin_request` (`user_id`);--> statement-breakpoint +CREATE INDEX `admin_request_userId_idx` ON `admin_request` (`user_id`);--> statement-breakpoint +CREATE INDEX `admin_request_status_idx` ON `admin_request` (`status`);--> statement-breakpoint +CREATE INDEX `account_userId_idx` ON `account` (`user_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint +CREATE INDEX `session_userId_idx` ON `session` (`user_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint +CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);--> statement-breakpoint +CREATE UNIQUE INDEX `registration_management_token_unique` ON `registration` (`management_token`);--> statement-breakpoint +CREATE INDEX `registration_email_idx` ON `registration` (`email`);--> statement-breakpoint +CREATE INDEX `registration_registrationType_idx` ON `registration` (`registration_type`);--> statement-breakpoint +CREATE INDEX `registration_artForm_idx` ON `registration` (`art_form`);--> statement-breakpoint +CREATE INDEX `registration_createdAt_idx` ON `registration` (`created_at`);--> statement-breakpoint +CREATE INDEX `registration_managementToken_idx` ON `registration` (`management_token`);--> statement-breakpoint +CREATE INDEX `registration_paymentStatus_idx` ON `registration` (`payment_status`);--> statement-breakpoint +CREATE INDEX `registration_giftAmount_idx` ON `registration` (`gift_amount`);--> statement-breakpoint +CREATE INDEX `registration_molliePaymentId_idx` ON `registration` (`mollie_payment_id`);--> statement-breakpoint +ALTER TABLE `registration` ADD `is_over_16` integer DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE `registration` ADD `drink_card_value` integer DEFAULT 0;--> statement-breakpoint +ALTER TABLE `registration` ADD `guests` text;--> statement-breakpoint +ALTER TABLE `registration` ADD `birthdate` text DEFAULT '' NOT NULL;--> statement-breakpoint +ALTER TABLE `registration` ADD `postcode` text DEFAULT '' NOT NULL;--> statement-breakpoint +ALTER TABLE `registration` ADD `payment_status` text DEFAULT 'pending' NOT NULL;--> statement-breakpoint +ALTER TABLE `registration` ADD `payment_amount` integer DEFAULT 0;--> statement-breakpoint +ALTER TABLE `registration` ADD `gift_amount` integer DEFAULT 0;--> statement-breakpoint +ALTER TABLE `registration` ADD `mollie_payment_id` text;--> statement-breakpoint +ALTER TABLE `registration` ADD `paid_at` integer;--> statement-breakpoint +ALTER TABLE `registration` ADD `drinkkaart_credited_at` integer;--> statement-breakpoint +ALTER TABLE `registration` ADD `payment_reminder_sent_at` integer; \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0008_snapshot.json b/packages/db/src/migrations/meta/0008_snapshot.json new file mode 100644 index 0000000..de27b1a --- /dev/null +++ b/packages/db/src/migrations/meta/0008_snapshot.json @@ -0,0 +1,971 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "7994e607-3835-43a5-9251-fca48f0aa19a", + "prevId": "d9a4f07e-e6ae-45d0-be82-c919ae7fbe09", + "tables": { + "admin_request": { + "name": "admin_request", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "requested_at": { + "name": "requested_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reviewed_by": { + "name": "reviewed_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "admin_request_user_id_unique": { + "name": "admin_request_user_id_unique", + "columns": ["user_id"], + "isUnique": true + }, + "admin_request_userId_idx": { + "name": "admin_request_userId_idx", + "columns": ["user_id"], + "isUnique": false + }, + "admin_request_status_idx": { + "name": "admin_request_status_idx", + "columns": ["status"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": ["token"], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": ["user_id"], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": ["identifier"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "drinkkaart": { + "name": "drinkkaart", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "qr_secret": { + "name": "qr_secret", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "drinkkaart_user_id_unique": { + "name": "drinkkaart_user_id_unique", + "columns": ["user_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "drinkkaart_topup": { + "name": "drinkkaart_topup", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "drinkkaart_id": { + "name": "drinkkaart_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "balance_before": { + "name": "balance_before", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "balance_after": { + "name": "balance_after", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mollie_payment_id": { + "name": "mollie_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "admin_id": { + "name": "admin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_at": { + "name": "paid_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "drinkkaart_topup_mollie_payment_id_unique": { + "name": "drinkkaart_topup_mollie_payment_id_unique", + "columns": ["mollie_payment_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "drinkkaart_transaction": { + "name": "drinkkaart_transaction", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "drinkkaart_id": { + "name": "drinkkaart_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "admin_id": { + "name": "admin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "balance_before": { + "name": "balance_before", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "balance_after": { + "name": "balance_after", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reversed_by": { + "name": "reversed_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reverses": { + "name": "reverses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "registration": { + "name": "registration", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registration_type": { + "name": "registration_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'watcher'" + }, + "art_form": { + "name": "art_form", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "experience": { + "name": "experience", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_over_16": { + "name": "is_over_16", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "drink_card_value": { + "name": "drink_card_value", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "guests": { + "name": "guests", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "birthdate": { + "name": "birthdate", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "postcode": { + "name": "postcode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "extra_questions": { + "name": "extra_questions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "management_token": { + "name": "management_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "payment_amount": { + "name": "payment_amount", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "gift_amount": { + "name": "gift_amount", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "mollie_payment_id": { + "name": "mollie_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_at": { + "name": "paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "drinkkaart_credited_at": { + "name": "drinkkaart_credited_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_reminder_sent_at": { + "name": "payment_reminder_sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "registration_management_token_unique": { + "name": "registration_management_token_unique", + "columns": ["management_token"], + "isUnique": true + }, + "registration_email_idx": { + "name": "registration_email_idx", + "columns": ["email"], + "isUnique": false + }, + "registration_registrationType_idx": { + "name": "registration_registrationType_idx", + "columns": ["registration_type"], + "isUnique": false + }, + "registration_artForm_idx": { + "name": "registration_artForm_idx", + "columns": ["art_form"], + "isUnique": false + }, + "registration_createdAt_idx": { + "name": "registration_createdAt_idx", + "columns": ["created_at"], + "isUnique": false + }, + "registration_managementToken_idx": { + "name": "registration_managementToken_idx", + "columns": ["management_token"], + "isUnique": false + }, + "registration_paymentStatus_idx": { + "name": "registration_paymentStatus_idx", + "columns": ["payment_status"], + "isUnique": false + }, + "registration_giftAmount_idx": { + "name": "registration_giftAmount_idx", + "columns": ["gift_amount"], + "isUnique": false + }, + "registration_molliePaymentId_idx": { + "name": "registration_molliePaymentId_idx", + "columns": ["mollie_payment_id"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reminder": { + "name": "reminder", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sent_24h_at": { + "name": "sent_24h_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "reminder_email_idx": { + "name": "reminder_email_idx", + "columns": ["email"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": { + "\"registration\".\"wants_to_perform\"": "\"registration\".\"registration_type\"" + } + }, + "internal": { + "indexes": {} + } +} diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 0a3ed89..a4a8284 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -57,6 +57,13 @@ "when": 1772931300000, "tag": "0007_reminder", "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1773910007494, + "tag": "0008_melodic_marrow", + "breakpoints": true } ] } diff --git a/packages/db/src/schema/registrations.ts b/packages/db/src/schema/registrations.ts index 0a57991..cdceb17 100644 --- a/packages/db/src/schema/registrations.ts +++ b/packages/db/src/schema/registrations.ts @@ -19,9 +19,11 @@ export const registration = sqliteTable( .default(false), // Watcher-specific fields drinkCardValue: integer("drink_card_value").default(0), - // Guests: JSON array of {firstName, lastName, email?, phone?} objects + // Guests: JSON array of {firstName, lastName, email?, phone?, birthdate, postcode} objects guests: text("guests"), // Shared + birthdate: text("birthdate").notNull().default(""), + postcode: text("postcode").notNull().default(""), extraQuestions: text("extra_questions"), managementToken: text("management_token").unique(), cancelledAt: integer("cancelled_at", { mode: "timestamp_ms" }),