Initial commit

Next.js + Express event management app for Hope Family Church.
This commit is contained in:
2026-07-23 15:26:47 +02:00
commit 3d381944d2
246 changed files with 57565 additions and 0 deletions
@@ -0,0 +1,342 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('admin', 'supervisor', 'staff', 'user');
-- CreateEnum
CREATE TYPE "RegistrationStatus" AS ENUM ('pending', 'confirmed', 'partial_paid', 'paid', 'cancelled');
-- CreateEnum
CREATE TYPE "FormFieldType" AS ENUM ('yes_no', 'text', 'date', 'numeric', 'statement', 'paragraph');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"phoneNumber" TEXT,
"updatedAt" TIMESTAMP(3) NOT NULL,
"role" "UserRole" NOT NULL DEFAULT 'user',
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Event" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"startDate" TIMESTAMP(3) NOT NULL,
"endDate" TIMESTAMP(3) NOT NULL,
"registrationDeadline" TIMESTAMP(3),
"goLiveAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"price" DOUBLE PRECISION NOT NULL,
"picture" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"createdById" TEXT,
"redirectUrl" TEXT,
CONSTRAINT "Event_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EventOption" (
"id" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"price" DOUBLE PRECISION NOT NULL,
"isMainTicket" BOOLEAN NOT NULL,
CONSTRAINT "EventOption_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EarlyBirdTier" (
"id" TEXT NOT NULL,
"eventOptionId" TEXT NOT NULL,
"deadline" TIMESTAMP(3) NOT NULL,
"price" DOUBLE PRECISION NOT NULL,
"order" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "EarlyBirdTier_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Registration" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"status" "RegistrationStatus" NOT NULL DEFAULT 'pending',
"checkoutId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Registration_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RegistrationOption" (
"id" TEXT NOT NULL,
"registrationId" TEXT NOT NULL,
"eventOptionId" TEXT NOT NULL,
"quantity" INTEGER NOT NULL DEFAULT 1,
CONSTRAINT "RegistrationOption_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Payment" (
"id" TEXT NOT NULL,
"amount" DOUBLE PRECISION NOT NULL,
"method" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"registrationId" TEXT,
"eventId" TEXT,
"isDonation" BOOLEAN NOT NULL DEFAULT false,
"externalId" TEXT,
"status" TEXT,
"originalPaymentId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Payment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Ticket" (
"id" TEXT NOT NULL,
"qrCode" TEXT NOT NULL,
"registrationOptionId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"isUsed" BOOLEAN NOT NULL DEFAULT false,
"emailSent" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TicketUsage" (
"id" TEXT NOT NULL,
"ticketId" TEXT NOT NULL,
"scannedById" TEXT NOT NULL,
"scannedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "TicketUsage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PasswordReset" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"used" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PasswordReset_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EventAttachment" (
"id" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"originalName" TEXT NOT NULL,
"filename" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
"size" INTEGER NOT NULL,
"url" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "EventAttachment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EventForm" (
"id" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"isRequired" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "EventForm_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EventFormField" (
"id" TEXT NOT NULL,
"formId" TEXT NOT NULL,
"type" "FormFieldType" NOT NULL,
"label" TEXT NOT NULL,
"isRequired" BOOLEAN NOT NULL DEFAULT false,
"order" INTEGER NOT NULL DEFAULT 0,
"helpText" TEXT,
"options" JSONB,
CONSTRAINT "EventFormField_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FormResponse" (
"id" TEXT NOT NULL,
"registrationId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "FormResponse_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FormAnswer" (
"id" TEXT NOT NULL,
"responseId" TEXT NOT NULL,
"fieldId" TEXT NOT NULL,
"value" TEXT NOT NULL,
CONSTRAINT "FormAnswer_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FormDraft" (
"id" TEXT NOT NULL,
"registrationId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"data" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FormDraft_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "YocoTransaction" (
"id" TEXT NOT NULL,
"externalId" TEXT NOT NULL,
"amount" INTEGER,
"currency" TEXT,
"createdDate" TIMESTAMP(3),
"checkoutId" TEXT,
"methodType" TEXT,
"reconciled" BOOLEAN NOT NULL DEFAULT false,
"ignored" BOOLEAN NOT NULL DEFAULT false,
"paymentId" TEXT,
"raw" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "YocoTransaction_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Event_redirectUrl_key" ON "Event"("redirectUrl");
-- CreateIndex
CREATE INDEX "Event_createdById_idx" ON "Event"("createdById");
-- CreateIndex
CREATE INDEX "EarlyBirdTier_eventOptionId_deadline_idx" ON "EarlyBirdTier"("eventOptionId", "deadline");
-- CreateIndex
CREATE UNIQUE INDEX "Registration_checkoutId_key" ON "Registration"("checkoutId");
-- CreateIndex
CREATE UNIQUE INDEX "Payment_externalId_key" ON "Payment"("externalId");
-- CreateIndex
CREATE UNIQUE INDEX "Ticket_qrCode_key" ON "Ticket"("qrCode");
-- CreateIndex
CREATE UNIQUE INDEX "PasswordReset_token_key" ON "PasswordReset"("token");
-- CreateIndex
CREATE UNIQUE INDEX "EventForm_eventId_key" ON "EventForm"("eventId");
-- CreateIndex
CREATE UNIQUE INDEX "FormDraft_registrationId_userId_key" ON "FormDraft"("registrationId", "userId");
-- CreateIndex
CREATE UNIQUE INDEX "YocoTransaction_externalId_key" ON "YocoTransaction"("externalId");
-- AddForeignKey
ALTER TABLE "Event" ADD CONSTRAINT "Event_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventOption" ADD CONSTRAINT "EventOption_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EarlyBirdTier" ADD CONSTRAINT "EarlyBirdTier_eventOptionId_fkey" FOREIGN KEY ("eventOptionId") REFERENCES "EventOption"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Registration" ADD CONSTRAINT "Registration_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Registration" ADD CONSTRAINT "Registration_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RegistrationOption" ADD CONSTRAINT "RegistrationOption_registrationId_fkey" FOREIGN KEY ("registrationId") REFERENCES "Registration"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RegistrationOption" ADD CONSTRAINT "RegistrationOption_eventOptionId_fkey" FOREIGN KEY ("eventOptionId") REFERENCES "EventOption"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_registrationId_fkey" FOREIGN KEY ("registrationId") REFERENCES "Registration"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_originalPaymentId_fkey" FOREIGN KEY ("originalPaymentId") REFERENCES "Payment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_registrationOptionId_fkey" FOREIGN KEY ("registrationOptionId") REFERENCES "RegistrationOption"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TicketUsage" ADD CONSTRAINT "TicketUsage_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "Ticket"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TicketUsage" ADD CONSTRAINT "TicketUsage_scannedById_fkey" FOREIGN KEY ("scannedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PasswordReset" ADD CONSTRAINT "PasswordReset_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventAttachment" ADD CONSTRAINT "EventAttachment_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventForm" ADD CONSTRAINT "EventForm_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventFormField" ADD CONSTRAINT "EventFormField_formId_fkey" FOREIGN KEY ("formId") REFERENCES "EventForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FormResponse" ADD CONSTRAINT "FormResponse_registrationId_fkey" FOREIGN KEY ("registrationId") REFERENCES "Registration"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FormAnswer" ADD CONSTRAINT "FormAnswer_responseId_fkey" FOREIGN KEY ("responseId") REFERENCES "FormResponse"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FormAnswer" ADD CONSTRAINT "FormAnswer_fieldId_fkey" FOREIGN KEY ("fieldId") REFERENCES "EventFormField"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FormDraft" ADD CONSTRAINT "FormDraft_registrationId_fkey" FOREIGN KEY ("registrationId") REFERENCES "Registration"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FormDraft" ADD CONSTRAINT "FormDraft_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "YocoTransaction" ADD CONSTRAINT "YocoTransaction_paymentId_fkey" FOREIGN KEY ("paymentId") REFERENCES "Payment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,34 @@
-- CreateTable
CREATE TABLE "Section" (
"id" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Section_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "SectionOption" (
"id" TEXT NOT NULL,
"sectionId" TEXT NOT NULL,
"eventOptionId" TEXT NOT NULL,
CONSTRAINT "SectionOption_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Section_eventId_idx" ON "Section"("eventId");
-- CreateIndex
CREATE UNIQUE INDEX "SectionOption_sectionId_eventOptionId_key" ON "SectionOption"("sectionId", "eventOptionId");
-- AddForeignKey
ALTER TABLE "Section" ADD CONSTRAINT "Section_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SectionOption" ADD CONSTRAINT "SectionOption_sectionId_fkey" FOREIGN KEY ("sectionId") REFERENCES "Section"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SectionOption" ADD CONSTRAINT "SectionOption_eventOptionId_fkey" FOREIGN KEY ("eventOptionId") REFERENCES "EventOption"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "failedAttempts" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "lockUntil" TIMESTAMP(3);
@@ -0,0 +1,2 @@
-- AlterTable: add tokenVersion for JWT revocation
ALTER TABLE "User" ADD COLUMN "tokenVersion" INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,9 @@
-- Add isHidden and requiresAuth to Event
ALTER TABLE "Event" ADD COLUMN "isHidden" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "Event" ADD COLUMN "requiresAuth" BOOLEAN NOT NULL DEFAULT true;
-- Add quantity to Ticket (1 ticket per option, not per qty unit)
ALTER TABLE "Ticket" ADD COLUMN "quantity" INTEGER NOT NULL DEFAULT 1;
-- Add quantityRedeemed to TicketUsage (for partial redemption)
ALTER TABLE "TicketUsage" ADD COLUMN "quantityRedeemed" INTEGER NOT NULL DEFAULT 1;
@@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[phoneNumber]` on the table `User` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "User_phoneNumber_key" ON "User"("phoneNumber");
@@ -0,0 +1,14 @@
-- CreateIndex
CREATE INDEX "Ticket_eventId_idx" ON "Ticket"("eventId");
-- CreateIndex
CREATE INDEX "Ticket_userId_idx" ON "Ticket"("userId");
-- CreateIndex
CREATE INDEX "TicketUsage_ticketId_idx" ON "TicketUsage"("ticketId");
-- CreateIndex
CREATE INDEX "TicketUsage_scannedAt_idx" ON "TicketUsage"("scannedAt");
-- CreateIndex
CREATE INDEX "TicketUsage_scannedById_scannedAt_idx" ON "TicketUsage"("scannedById", "scannedAt");
@@ -0,0 +1,56 @@
-- CreateIndex
CREATE INDEX "EventAttachment_eventId_idx" ON "EventAttachment"("eventId");
-- CreateIndex
CREATE INDEX "EventFormField_formId_idx" ON "EventFormField"("formId");
-- CreateIndex
CREATE INDEX "EventOption_eventId_idx" ON "EventOption"("eventId");
-- CreateIndex
CREATE INDEX "FormAnswer_responseId_idx" ON "FormAnswer"("responseId");
-- CreateIndex
CREATE INDEX "FormAnswer_fieldId_idx" ON "FormAnswer"("fieldId");
-- CreateIndex
CREATE INDEX "FormResponse_registrationId_idx" ON "FormResponse"("registrationId");
-- CreateIndex
CREATE INDEX "PasswordReset_userId_idx" ON "PasswordReset"("userId");
-- CreateIndex
CREATE INDEX "Payment_userId_idx" ON "Payment"("userId");
-- CreateIndex
CREATE INDEX "Payment_registrationId_idx" ON "Payment"("registrationId");
-- CreateIndex
CREATE INDEX "Payment_eventId_idx" ON "Payment"("eventId");
-- CreateIndex
CREATE INDEX "Payment_createdAt_idx" ON "Payment"("createdAt");
-- CreateIndex
CREATE INDEX "Registration_userId_idx" ON "Registration"("userId");
-- CreateIndex
CREATE INDEX "Registration_eventId_idx" ON "Registration"("eventId");
-- CreateIndex
CREATE INDEX "Registration_userId_status_idx" ON "Registration"("userId", "status");
-- CreateIndex
CREATE INDEX "Registration_eventId_status_idx" ON "Registration"("eventId", "status");
-- CreateIndex
CREATE INDEX "RegistrationOption_registrationId_idx" ON "RegistrationOption"("registrationId");
-- CreateIndex
CREATE INDEX "RegistrationOption_eventOptionId_idx" ON "RegistrationOption"("eventOptionId");
-- CreateIndex
CREATE INDEX "Ticket_registrationOptionId_idx" ON "Ticket"("registrationOptionId");
-- CreateIndex
CREATE INDEX "YocoTransaction_paymentId_idx" ON "YocoTransaction"("paymentId");
@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "NotificationPreference" AS ENUM ('email', 'whatsapp', 'both');
-- AlterTable
ALTER TABLE "User" ADD COLUMN "notificationPreference" "NotificationPreference" NOT NULL DEFAULT 'email';
@@ -0,0 +1,8 @@
-- CreateTable
CREATE TABLE "AppSetting" (
"key" TEXT NOT NULL,
"value" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AppSetting_pkey" PRIMARY KEY ("key")
);
@@ -0,0 +1,37 @@
-- Add stockLimit to EventOption
ALTER TABLE "EventOption" ADD COLUMN "stockLimit" INTEGER NOT NULL DEFAULT 0;
-- Add stockLimit to EarlyBirdTier
ALTER TABLE "EarlyBirdTier" ADD COLUMN "stockLimit" INTEGER NOT NULL DEFAULT 0;
-- CreateTable OptionVariant
CREATE TABLE "OptionVariant" (
"id" TEXT NOT NULL,
"eventOptionId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"price" DOUBLE PRECISION,
"stockLimit" INTEGER NOT NULL DEFAULT 0,
"order" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "OptionVariant_pkey" PRIMARY KEY ("id")
);
-- Add new columns to RegistrationOption
ALTER TABLE "RegistrationOption" ADD COLUMN "variantId" TEXT;
ALTER TABLE "RegistrationOption" ADD COLUMN "appliedTierId" TEXT;
ALTER TABLE "RegistrationOption" ADD COLUMN "priceSnapshot" DOUBLE PRECISION;
-- CreateIndex
CREATE INDEX "OptionVariant_eventOptionId_idx" ON "OptionVariant"("eventOptionId");
CREATE INDEX "RegistrationOption_variantId_idx" ON "RegistrationOption"("variantId");
CREATE INDEX "RegistrationOption_appliedTierId_idx" ON "RegistrationOption"("appliedTierId");
-- AddForeignKey
ALTER TABLE "OptionVariant" ADD CONSTRAINT "OptionVariant_eventOptionId_fkey"
FOREIGN KEY ("eventOptionId") REFERENCES "EventOption"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "RegistrationOption" ADD CONSTRAINT "RegistrationOption_variantId_fkey"
FOREIGN KEY ("variantId") REFERENCES "OptionVariant"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "RegistrationOption" ADD CONSTRAINT "RegistrationOption_appliedTierId_fkey"
FOREIGN KEY ("appliedTierId") REFERENCES "EarlyBirdTier"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "EarlyBirdTier" ADD COLUMN "variantId" TEXT;
-- CreateIndex
CREATE INDEX "EarlyBirdTier_variantId_idx" ON "EarlyBirdTier"("variantId");
-- AddForeignKey
ALTER TABLE "EarlyBirdTier" ADD CONSTRAINT "EarlyBirdTier_variantId_fkey" FOREIGN KEY ("variantId") REFERENCES "OptionVariant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,213 @@
-- CreateTable
CREATE TABLE "Ministry" (
"id" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"image" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"order" INTEGER NOT NULL DEFAULT 0,
"leaderId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Ministry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TeamMember" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"title" TEXT NOT NULL,
"bio" TEXT,
"photo" TEXT,
"order" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"email" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TeamMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ContentBlock" (
"id" TEXT NOT NULL,
"page" TEXT NOT NULL,
"key" TEXT NOT NULL,
"value" TEXT NOT NULL,
"type" TEXT NOT NULL DEFAULT 'text',
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ContentBlock_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ChurchDocument" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"fileUrl" TEXT NOT NULL,
"category" TEXT NOT NULL DEFAULT 'other',
"isPublic" BOOLEAN NOT NULL DEFAULT true,
"uploadedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ChurchDocument_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FellowshipGroup" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"leader" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"order" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FellowshipGroup_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ChurchMember" (
"id" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"mainName" TEXT NOT NULL,
"mainEmail" TEXT NOT NULL,
"mainPhone" TEXT,
"mainBirthday" TIMESTAMP(3),
"mainOccupation" TEXT,
"mainEmploymentStatus" TEXT,
"mainDateOfSalvation" TIMESTAMP(3),
"mainDateOfWaterBaptism" TIMESTAMP(3),
"mainHolySpiritBaptism" BOOLEAN NOT NULL DEFAULT false,
"spouseName" TEXT,
"spouseEmail" TEXT,
"spousePhone" TEXT,
"spouseBirthday" TIMESTAMP(3),
"spouseOccupation" TEXT,
"spouseEmploymentStatus" TEXT,
"spouseDateOfSalvation" TIMESTAMP(3),
"spouseDateOfWaterBaptism" TIMESTAMP(3),
"spouseHolySpiritBaptism" BOOLEAN,
"homeAddress" TEXT,
"inFellowshipGroup" BOOLEAN NOT NULL DEFAULT false,
"fellowshipGroupId" TEXT,
"membershipConfirmed" BOOLEAN NOT NULL DEFAULT false,
"notes" TEXT,
"appliedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"approvedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ChurchMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ChurchChild" (
"id" TEXT NOT NULL,
"memberId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"birthday" TIMESTAMP(3),
CONSTRAINT "ChurchChild_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MinistryInterest" (
"id" TEXT NOT NULL,
"memberId" TEXT NOT NULL,
"ministryId" TEXT NOT NULL,
"forSpouse" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "MinistryInterest_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Donation" (
"id" TEXT NOT NULL,
"donorName" TEXT,
"donorEmail" TEXT,
"donorPhone" TEXT,
"amount" DOUBLE PRECISION NOT NULL,
"method" TEXT NOT NULL,
"reference" TEXT,
"note" TEXT,
"isAnonymous" BOOLEAN NOT NULL DEFAULT false,
"linkedMemberId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Donation_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Ministry_slug_key" ON "Ministry"("slug");
-- CreateIndex
CREATE INDEX "Ministry_isActive_order_idx" ON "Ministry"("isActive", "order");
-- CreateIndex
CREATE INDEX "Ministry_leaderId_idx" ON "Ministry"("leaderId");
-- CreateIndex
CREATE INDEX "TeamMember_isActive_order_idx" ON "TeamMember"("isActive", "order");
-- CreateIndex
CREATE INDEX "ContentBlock_page_idx" ON "ContentBlock"("page");
-- CreateIndex
CREATE UNIQUE INDEX "ContentBlock_page_key_key" ON "ContentBlock"("page", "key");
-- CreateIndex
CREATE INDEX "FellowshipGroup_isActive_order_idx" ON "FellowshipGroup"("isActive", "order");
-- CreateIndex
CREATE INDEX "ChurchMember_status_idx" ON "ChurchMember"("status");
-- CreateIndex
CREATE INDEX "ChurchMember_mainEmail_idx" ON "ChurchMember"("mainEmail");
-- CreateIndex
CREATE INDEX "ChurchMember_fellowshipGroupId_idx" ON "ChurchMember"("fellowshipGroupId");
-- CreateIndex
CREATE INDEX "ChurchChild_memberId_idx" ON "ChurchChild"("memberId");
-- CreateIndex
CREATE INDEX "MinistryInterest_memberId_idx" ON "MinistryInterest"("memberId");
-- CreateIndex
CREATE INDEX "MinistryInterest_ministryId_idx" ON "MinistryInterest"("ministryId");
-- CreateIndex
CREATE UNIQUE INDEX "MinistryInterest_memberId_ministryId_forSpouse_key" ON "MinistryInterest"("memberId", "ministryId", "forSpouse");
-- CreateIndex
CREATE INDEX "Donation_linkedMemberId_idx" ON "Donation"("linkedMemberId");
-- CreateIndex
CREATE INDEX "Donation_createdAt_idx" ON "Donation"("createdAt");
-- CreateIndex
CREATE INDEX "Donation_method_idx" ON "Donation"("method");
-- AddForeignKey
ALTER TABLE "Ministry" ADD CONSTRAINT "Ministry_leaderId_fkey" FOREIGN KEY ("leaderId") REFERENCES "TeamMember"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ChurchMember" ADD CONSTRAINT "ChurchMember_fellowshipGroupId_fkey" FOREIGN KEY ("fellowshipGroupId") REFERENCES "FellowshipGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ChurchChild" ADD CONSTRAINT "ChurchChild_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES "ChurchMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MinistryInterest" ADD CONSTRAINT "MinistryInterest_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES "ChurchMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "MinistryInterest" ADD CONSTRAINT "MinistryInterest_ministryId_fkey" FOREIGN KEY ("ministryId") REFERENCES "Ministry"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Donation" ADD CONSTRAINT "Donation_linkedMemberId_fkey" FOREIGN KEY ("linkedMemberId") REFERENCES "ChurchMember"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,158 @@
/*
Warnings:
- You are about to drop the `ChurchChild` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `ChurchDocument` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `ChurchMember` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `ContentBlock` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `Donation` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `FellowshipGroup` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `Ministry` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `MinistryInterest` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `TeamMember` table. If the table is not empty, all the data it contains will be lost.
*/
-- CreateEnum
CREATE TYPE "CashupStatus" AS ENUM ('open', 'closed');
-- CreateEnum
CREATE TYPE "CashupAction" AS ENUM ('closed', 'quick_closed', 'reopened');
-- CreateEnum
CREATE TYPE "EventCostType" AS ENUM ('once_off', 'per_item');
-- DropForeignKey
ALTER TABLE "ChurchChild" DROP CONSTRAINT "ChurchChild_memberId_fkey";
-- DropForeignKey
ALTER TABLE "ChurchMember" DROP CONSTRAINT "ChurchMember_fellowshipGroupId_fkey";
-- DropForeignKey
ALTER TABLE "Donation" DROP CONSTRAINT "Donation_linkedMemberId_fkey";
-- DropForeignKey
ALTER TABLE "Ministry" DROP CONSTRAINT "Ministry_leaderId_fkey";
-- DropForeignKey
ALTER TABLE "MinistryInterest" DROP CONSTRAINT "MinistryInterest_memberId_fkey";
-- DropForeignKey
ALTER TABLE "MinistryInterest" DROP CONSTRAINT "MinistryInterest_ministryId_fkey";
-- AlterTable
ALTER TABLE "Event" ADD COLUMN "cashupDraft" JSONB,
ADD COLUMN "cashupStatus" "CashupStatus" NOT NULL DEFAULT 'open',
ADD COLUMN "closedAt" TIMESTAMP(3),
ADD COLUMN "closedById" TEXT,
ADD COLUMN "reopenedAt" TIMESTAMP(3),
ADD COLUMN "reopenedById" TEXT;
-- DropTable
DROP TABLE "ChurchChild";
-- DropTable
DROP TABLE "ChurchDocument";
-- DropTable
DROP TABLE "ChurchMember";
-- DropTable
DROP TABLE "ContentBlock";
-- DropTable
DROP TABLE "Donation";
-- DropTable
DROP TABLE "FellowshipGroup";
-- DropTable
DROP TABLE "Ministry";
-- DropTable
DROP TABLE "MinistryInterest";
-- DropTable
DROP TABLE "TeamMember";
-- CreateTable
CREATE TABLE "EventCost" (
"id" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"eventOptionId" TEXT,
"label" TEXT NOT NULL,
"costType" "EventCostType" NOT NULL,
"amount" DOUBLE PRECISION NOT NULL,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "EventCost_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EventCashup" (
"id" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"action" "CashupAction" NOT NULL,
"donationsWrittenOff" DOUBLE PRECISION NOT NULL DEFAULT 0,
"totalCosts" DOUBLE PRECISION NOT NULL DEFAULT 0,
"totalExpectedRevenue" DOUBLE PRECISION NOT NULL DEFAULT 0,
"totalActualRevenue" DOUBLE PRECISION,
"notes" TEXT,
"performedById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "EventCashup_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EventCashupLine" (
"id" TEXT NOT NULL,
"cashupId" TEXT NOT NULL,
"method" TEXT NOT NULL,
"expectedAmount" DOUBLE PRECISION NOT NULL DEFAULT 0,
"actualAmount" DOUBLE PRECISION,
"variance" DOUBLE PRECISION,
"notes" TEXT,
CONSTRAINT "EventCashupLine_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "EventCost_eventId_idx" ON "EventCost"("eventId");
-- CreateIndex
CREATE INDEX "EventCost_eventOptionId_idx" ON "EventCost"("eventOptionId");
-- CreateIndex
CREATE INDEX "EventCashup_eventId_createdAt_idx" ON "EventCashup"("eventId", "createdAt");
-- CreateIndex
CREATE INDEX "EventCashupLine_cashupId_idx" ON "EventCashupLine"("cashupId");
-- CreateIndex
CREATE INDEX "Event_closedById_idx" ON "Event"("closedById");
-- CreateIndex
CREATE INDEX "Event_reopenedById_idx" ON "Event"("reopenedById");
-- AddForeignKey
ALTER TABLE "Event" ADD CONSTRAINT "Event_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Event" ADD CONSTRAINT "Event_reopenedById_fkey" FOREIGN KEY ("reopenedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventCost" ADD CONSTRAINT "EventCost_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventCost" ADD CONSTRAINT "EventCost_eventOptionId_fkey" FOREIGN KEY ("eventOptionId") REFERENCES "EventOption"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventCashup" ADD CONSTRAINT "EventCashup_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventCashup" ADD CONSTRAINT "EventCashup_performedById_fkey" FOREIGN KEY ("performedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EventCashupLine" ADD CONSTRAINT "EventCashupLine_cashupId_fkey" FOREIGN KEY ("cashupId") REFERENCES "EventCashup"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,21 @@
-- AlterTable: rename donationsWrittenOff -> unallocatedDonationsTotal, preserving existing values
ALTER TABLE "EventCashup" RENAME COLUMN "donationsWrittenOff" TO "unallocatedDonationsTotal";
-- AlterTable
ALTER TABLE "EventCost" ADD COLUMN "paidFromMethod" TEXT;
-- CreateTable
CREATE TABLE "EventCashupDenomination" (
"id" TEXT NOT NULL,
"lineId" TEXT NOT NULL,
"value" DOUBLE PRECISION NOT NULL,
"count" INTEGER NOT NULL,
CONSTRAINT "EventCashupDenomination_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "EventCashupDenomination_lineId_idx" ON "EventCashupDenomination"("lineId");
-- AddForeignKey
ALTER TABLE "EventCashupDenomination" ADD CONSTRAINT "EventCashupDenomination_lineId_fkey" FOREIGN KEY ("lineId") REFERENCES "EventCashupLine"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "_EventNotifyRecipients" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "_EventNotifyRecipients_AB_unique" ON "_EventNotifyRecipients"("A", "B");
-- CreateIndex
CREATE INDEX "_EventNotifyRecipients_B_index" ON "_EventNotifyRecipients"("B");
-- AddForeignKey
ALTER TABLE "_EventNotifyRecipients" ADD CONSTRAINT "_EventNotifyRecipients_A_fkey" FOREIGN KEY ("A") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_EventNotifyRecipients" ADD CONSTRAINT "_EventNotifyRecipients_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
+636
View File
@@ -0,0 +1,636 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum UserRole {
admin
supervisor
staff
user
}
enum NotificationPreference {
email
whatsapp
both
}
enum RegistrationStatus {
pending
confirmed
partial_paid
paid
cancelled
}
enum CashupStatus {
open
closed
}
enum CashupAction {
closed
quick_closed
reopened
}
enum EventCostType {
once_off
per_item
}
model User {
id String @id @default(uuid())
name String
email String @unique
password String
createdAt DateTime @default(now())
isActive Boolean @default(true)
phoneNumber String? @unique
updatedAt DateTime @updatedAt
role UserRole @default(user)
failedAttempts Int @default(0)
lockUntil DateTime?
tokenVersion Int @default(0)
notificationPreference NotificationPreference @default(email)
registrations Registration[]
payments Payment[]
tickets Ticket[]
ticketScans TicketUsage[]
passwordResets PasswordReset[]
Event Event[]
FormDraft FormDraft[]
eventsClosed Event[] @relation("EventClosedBy")
eventsReopened Event[] @relation("EventReopenedBy")
cashupsPerformed EventCashup[]
notifyForEvents Event[] @relation("EventNotifyRecipients")
}
model Event {
id String @id @default(uuid())
title String
description String?
startDate DateTime
endDate DateTime
registrationDeadline DateTime?
goLiveAt DateTime @default(now())
price Float
picture String?
isActive Boolean @default(true)
isHidden Boolean @default(false)
requiresAuth Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String?
createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull)
redirectUrl String? @unique
eventOptions EventOption[]
registrations Registration[]
payments Payment[]
tickets Ticket[]
attachments EventAttachment[]
form EventForm?
Section Section[]
cashupStatus CashupStatus @default(open)
cashupDraft Json?
closedAt DateTime?
closedById String?
closedBy User? @relation("EventClosedBy", fields: [closedById], references: [id], onDelete: SetNull)
reopenedAt DateTime?
reopenedById String?
reopenedBy User? @relation("EventReopenedBy", fields: [reopenedById], references: [id], onDelete: SetNull)
costs EventCost[]
cashups EventCashup[]
// Users who should receive registration/payment/daily-summary notifications for this
// event. Falls back to `createdBy` when empty (see backend/src/utils/notifications.js).
notifyRecipients User[] @relation("EventNotifyRecipients")
@@index([createdById])
@@index([closedById])
@@index([reopenedById])
}
model EventOption {
id String @id @default(uuid())
eventId String
name String
price Float
isMainTicket Boolean
stockLimit Int @default(0)
event Event @relation(fields: [eventId], references: [id], onDelete: Restrict)
registrationOptions RegistrationOption[]
earlyBirdTiers EarlyBirdTier[]
SectionOption SectionOption[]
variants OptionVariant[]
costs EventCost[]
@@index([eventId])
}
model EarlyBirdTier {
id String @id @default(uuid())
eventOptionId String
variantId String?
deadline DateTime
price Float
order Int @default(0)
stockLimit Int @default(0)
eventOption EventOption @relation(fields: [eventOptionId], references: [id], onDelete: Cascade)
variant OptionVariant? @relation(fields: [variantId], references: [id], onDelete: Cascade)
registrationOptions RegistrationOption[]
@@index([eventOptionId, deadline])
@@index([variantId])
}
model OptionVariant {
id String @id @default(uuid())
eventOptionId String
name String
price Float?
stockLimit Int @default(0)
order Int @default(0)
eventOption EventOption @relation(fields: [eventOptionId], references: [id], onDelete: Cascade)
registrationOptions RegistrationOption[]
earlyBirdTiers EarlyBirdTier[]
@@index([eventOptionId])
}
model Registration {
id String @id @default(uuid())
userId String
eventId String
status RegistrationStatus @default(pending)
checkoutId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
event Event @relation(fields: [eventId], references: [id], onDelete: Restrict)
registrationOptions RegistrationOption[]
payments Payment[]
formResponses FormResponse[]
formDrafts FormDraft[]
@@index([userId])
@@index([eventId])
@@index([userId, status])
@@index([eventId, status])
}
model RegistrationOption {
id String @id @default(uuid())
registrationId String
eventOptionId String
quantity Int @default(1)
variantId String?
appliedTierId String?
priceSnapshot Float?
registration Registration @relation(fields: [registrationId], references: [id], onDelete: Restrict)
eventOption EventOption @relation(fields: [eventOptionId], references: [id], onDelete: Restrict)
variant OptionVariant? @relation(fields: [variantId], references: [id], onDelete: SetNull)
appliedTier EarlyBirdTier? @relation(fields: [appliedTierId], references: [id], onDelete: SetNull)
tickets Ticket[]
@@index([registrationId])
@@index([eventOptionId])
@@index([variantId])
@@index([appliedTierId])
}
model Payment {
id String @id @default(uuid())
amount Float
method String
userId String
registrationId String?
eventId String?
isDonation Boolean @default(false)
externalId String? @unique
status String?
originalPaymentId String?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
registration Registration? @relation(fields: [registrationId], references: [id], onDelete: SetNull)
event Event? @relation(fields: [eventId], references: [id], onDelete: SetNull)
originalPayment Payment? @relation("SplitPayments", fields: [originalPaymentId], references: [id], onDelete: SetNull)
splitPayments Payment[] @relation("SplitPayments")
YocoTransaction YocoTransaction[]
@@index([userId])
@@index([registrationId])
@@index([eventId])
@@index([createdAt])
}
model Ticket {
id String @id @default(uuid())
qrCode String @unique
registrationOptionId String
userId String
eventId String
quantity Int @default(1)
isUsed Boolean @default(false)
emailSent Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
registrationOption RegistrationOption @relation(fields: [registrationOptionId], references: [id], onDelete: Restrict)
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
event Event @relation(fields: [eventId], references: [id], onDelete: Restrict)
usages TicketUsage[]
@@index([eventId])
@@index([userId])
@@index([registrationOptionId])
}
model TicketUsage {
id String @id @default(uuid())
ticketId String
scannedById String
scannedAt DateTime @default(now())
quantityRedeemed Int @default(1)
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Restrict)
scannedBy User @relation(fields: [scannedById], references: [id], onDelete: Restrict)
@@index([ticketId])
@@index([scannedAt])
@@index([scannedById, scannedAt])
}
model PasswordReset {
id String @id @default(uuid())
userId String
token String @unique
expiresAt DateTime
used Boolean @default(false)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
model EventAttachment {
id String @id @default(uuid())
eventId String
originalName String
filename String
mimeType String
size Int
url String
createdAt DateTime @default(now())
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
@@index([eventId])
}
// Event registration forms
enum FormFieldType {
yes_no
text
date
numeric
statement
paragraph
}
model EventForm {
id String @id @default(uuid())
eventId String @unique
isRequired Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
fields EventFormField[]
}
model EventFormField {
id String @id @default(uuid())
formId String
type FormFieldType
label String
isRequired Boolean @default(false)
order Int @default(0)
helpText String?
options Json?
form EventForm @relation(fields: [formId], references: [id], onDelete: Cascade)
answers FormAnswer[]
@@index([formId])
}
model FormResponse {
id String @id @default(uuid())
registrationId String
createdAt DateTime @default(now())
registration Registration @relation(fields: [registrationId], references: [id], onDelete: Cascade)
answers FormAnswer[]
@@index([registrationId])
}
model FormAnswer {
id String @id @default(uuid())
responseId String
fieldId String
value String
response FormResponse @relation(fields: [responseId], references: [id], onDelete: Cascade)
field EventFormField @relation(fields: [fieldId], references: [id], onDelete: Cascade)
@@index([responseId])
@@index([fieldId])
}
// Draft storage for in-progress attendee forms
model FormDraft {
id String @id @default(uuid())
registrationId String
userId String
data Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
registration Registration @relation(fields: [registrationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([registrationId, userId])
}
// Stores all webhook events from Yoco. If a Payment is created (registration or donation),
// the record is marked reconciled and linked via paymentId. Otherwise, remains unreconciled.
model YocoTransaction {
id String @id @default(uuid())
externalId String @unique // Yoco payment/event id
amount Int?
currency String?
createdDate DateTime?
checkoutId String?
methodType String? // paymentMethodDetails.type (e.g., card)
reconciled Boolean @default(false)
ignored Boolean @default(false)
paymentId String?
payment Payment? @relation(fields: [paymentId], references: [id], onDelete: SetNull)
raw Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([paymentId])
}
model Section {
id String @id @default(uuid())
eventId String
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
allowedOptions SectionOption[]
@@index([eventId])
}
model SectionOption {
id String @id @default(uuid())
sectionId String
eventOptionId String
section Section @relation(fields: [sectionId], references: [id], onDelete: Cascade)
eventOption EventOption @relation(fields: [eventOptionId], references: [id], onDelete: Cascade)
@@unique([sectionId, eventOptionId])
}
model AppSetting {
key String @id
value String
updatedAt DateTime @updatedAt
}
model EventCost {
id String @id @default(uuid())
eventId String
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
eventOptionId String?
eventOption EventOption? @relation(fields: [eventOptionId], references: [id], onDelete: Cascade)
label String
costType EventCostType
amount Float
paidFromMethod String? // 'cash' | 'card' | 'eft' | 'other' | null — null = not paid from event takings
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([eventId])
@@index([eventOptionId])
}
// Append-only audit trail: one row per close / quick-close / reopen. Never updated or deleted.
model EventCashup {
id String @id @default(uuid())
eventId String
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
action CashupAction
unallocatedDonationsTotal Float @default(0)
totalCosts Float @default(0)
totalExpectedRevenue Float @default(0)
totalActualRevenue Float?
notes String?
performedById String?
performedBy User? @relation(fields: [performedById], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
lines EventCashupLine[]
@@index([eventId, createdAt])
}
model EventCashupLine {
id String @id @default(uuid())
cashupId String
cashup EventCashup @relation(fields: [cashupId], references: [id], onDelete: Cascade)
method String
expectedAmount Float @default(0)
actualAmount Float?
variance Float?
notes String?
denominations EventCashupDenomination[]
@@index([cashupId])
}
model EventCashupDenomination {
id String @id @default(uuid())
lineId String
line EventCashupLine @relation(fields: [lineId], references: [id], onDelete: Cascade)
value Float
count Int
@@index([lineId])
}
// ─── Church Website Models (disabled — kept for reference, not active Prisma models) ──
//
// model Ministry {
// id String @id @default(uuid())
// slug String @unique
// title String
// description String?
// image String?
// isActive Boolean @default(true)
// order Int @default(0)
// leaderId String?
// leader TeamMember? @relation(fields: [leaderId], references: [id], onDelete: SetNull)
// interests MinistryInterest[]
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
//
// @@index([isActive, order])
// @@index([leaderId])
// }
//
// model TeamMember {
// id String @id @default(uuid())
// name String
// title String
// bio String?
// photo String?
// order Int @default(0)
// isActive Boolean @default(true)
// email String?
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
// ministries Ministry[]
//
// @@index([isActive, order])
// }
//
// model ContentBlock {
// id String @id @default(uuid())
// page String
// key String
// value String
// type String @default("text")
// updatedAt DateTime @updatedAt
//
// @@unique([page, key])
// @@index([page])
// }
//
// model ChurchDocument {
// id String @id @default(uuid())
// title String
// description String?
// fileUrl String
// category String @default("other")
// isPublic Boolean @default(true)
// uploadedAt DateTime @default(now())
// updatedAt DateTime @updatedAt
// }
//
// model FellowshipGroup {
// id String @id @default(uuid())
// name String
// description String?
// leader String?
// isActive Boolean @default(true)
// order Int @default(0)
// members ChurchMember[]
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
//
// @@index([isActive, order])
// }
//
// model ChurchMember {
// id String @id @default(uuid())
// status String @default("pending")
// mainName String
// mainEmail String
// mainPhone String?
// mainBirthday DateTime?
// mainOccupation String?
// mainEmploymentStatus String?
// mainDateOfSalvation DateTime?
// mainDateOfWaterBaptism DateTime?
// mainHolySpiritBaptism Boolean @default(false)
// spouseName String?
// spouseEmail String?
// spousePhone String?
// spouseBirthday DateTime?
// spouseOccupation String?
// spouseEmploymentStatus String?
// spouseDateOfSalvation DateTime?
// spouseDateOfWaterBaptism DateTime?
// spouseHolySpiritBaptism Boolean?
// homeAddress String?
// inFellowshipGroup Boolean @default(false)
// fellowshipGroupId String?
// fellowshipGroup FellowshipGroup? @relation(fields: [fellowshipGroupId], references: [id], onDelete: SetNull)
// membershipConfirmed Boolean @default(false)
// notes String?
// appliedAt DateTime @default(now())
// approvedAt DateTime?
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
// children ChurchChild[]
// ministryInterests MinistryInterest[]
// donations Donation[]
//
// @@index([status])
// @@index([mainEmail])
// @@index([fellowshipGroupId])
// }
//
// model ChurchChild {
// id String @id @default(uuid())
// memberId String
// name String
// birthday DateTime?
// member ChurchMember @relation(fields: [memberId], references: [id], onDelete: Cascade)
//
// @@index([memberId])
// }
//
// model MinistryInterest {
// id String @id @default(uuid())
// memberId String
// ministryId String
// forSpouse Boolean @default(false)
// member ChurchMember @relation(fields: [memberId], references: [id], onDelete: Cascade)
// ministry Ministry @relation(fields: [ministryId], references: [id], onDelete: Cascade)
//
// @@unique([memberId, ministryId, forSpouse])
// @@index([memberId])
// @@index([ministryId])
// }
//
// model Donation {
// id String @id @default(uuid())
// donorName String?
// donorEmail String?
// donorPhone String?
// amount Float
// method String
// reference String?
// note String?
// isAnonymous Boolean @default(false)
// linkedMemberId String?
// linkedMember ChurchMember? @relation(fields: [linkedMemberId], references: [id], onDelete: SetNull)
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
//
// @@index([linkedMemberId])
// @@index([createdAt])
// @@index([method])
// }