This commit is contained in:
commit
59bb5a38a3
132 files changed
+19435
No files matched your search
@@ -0,0 +1,528 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PricePhase" AS ENUM ('PREVIEW', 'EARLY_BIRD', 'REGULAR');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BookingStatus" AS ENUM ('PENDING', 'CONFIRMED', 'CANCELED', 'EXPIRED', 'NEEDS_REVIEW');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PaymentStatus" AS ENUM ('UNPAID', 'PROCESSING', 'PARTIALLY_PAID', 'PAID', 'FAILED', 'PARTIALLY_REFUNDED', 'REFUNDED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PlanType" AS ENUM ('FULL', 'TWO_INSTALLMENTS', 'THREE_INSTALLMENTS');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "InstallmentStatus" AS ENUM ('PENDING', 'PROCESSING', 'PAID', 'FAILED', 'REQUIRES_ACTION', 'CANCELED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ReservationStatus" AS ENUM ('ACTIVE', 'HELD', 'CONVERTED', 'RELEASED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RedemptionStatus" AS ENUM ('RESERVED', 'COMPLETED', 'RELEASED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StripeEventStatus" AS ENUM ('PROCESSING', 'PROCESSED', 'IGNORED', 'FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EmailStatus" AS ENUM ('PENDING', 'SENT', 'FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ActorType" AS ENUM ('ADMIN', 'CUSTOMER', 'STRIPE', 'WORKER', 'SYSTEM');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Camp" (
|
||||
"id" TEXT NOT NULL,
|
||||
"salesEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"capacity" INTEGER NOT NULL DEFAULT 10,
|
||||
"capacityConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"stammmodelQuota" INTEGER NOT NULL DEFAULT 4,
|
||||
"stammmodelQuotaConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"campStartDate" DATE,
|
||||
"campEndDate" DATE,
|
||||
"dateConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"locationName" TEXT,
|
||||
"locationRegion" TEXT,
|
||||
"locationConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"accommodationText" TEXT,
|
||||
"accommodationConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"servicesConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"organizerName" TEXT,
|
||||
"organizerAddress" TEXT,
|
||||
"organizerEmail" TEXT,
|
||||
"organizerPhone" TEXT,
|
||||
"organizerConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"taxNote" TEXT,
|
||||
"taxConfirmed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"termsVersion" TEXT,
|
||||
"termsUrl" TEXT,
|
||||
"termsApproved" BOOLEAN NOT NULL DEFAULT false,
|
||||
"travelLawChecked" BOOLEAN NOT NULL DEFAULT false,
|
||||
"installmentsApproved" BOOLEAN NOT NULL DEFAULT false,
|
||||
"installmentRuleApproved" BOOLEAN NOT NULL DEFAULT false,
|
||||
"reservationMinutes" INTEGER NOT NULL DEFAULT 30,
|
||||
"finalInstallmentLeadDays" INTEGER NOT NULL DEFAULT 30,
|
||||
"minInstallmentGapDays" INTEGER NOT NULL DEFAULT 14,
|
||||
"testPhaseOverride" "PricePhase",
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"updatedBy" TEXT,
|
||||
|
||||
CONSTRAINT "Camp_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Booking" (
|
||||
"id" TEXT NOT NULL,
|
||||
"reference" TEXT NOT NULL,
|
||||
"campId" TEXT NOT NULL,
|
||||
"status" "BookingStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"paymentStatus" "PaymentStatus" NOT NULL DEFAULT 'UNPAID',
|
||||
"livemode" BOOLEAN NOT NULL,
|
||||
"idempotencyKey" TEXT NOT NULL,
|
||||
"statusTokenHash" TEXT NOT NULL,
|
||||
"firstName" TEXT NOT NULL,
|
||||
"lastName" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"phone" TEXT NOT NULL,
|
||||
"country" TEXT NOT NULL,
|
||||
"notes" TEXT,
|
||||
"adultConfirmedAt" TIMESTAMP(3) NOT NULL,
|
||||
"termsAcceptedAt" TIMESTAMP(3) NOT NULL,
|
||||
"termsVersion" TEXT NOT NULL,
|
||||
"accommodationAcknowledgedAt" TIMESTAMP(3) NOT NULL,
|
||||
"privacyAcknowledgedAt" TIMESTAMP(3) NOT NULL,
|
||||
"installmentMandateAt" TIMESTAMP(3),
|
||||
"pricePhase" "PricePhase" NOT NULL,
|
||||
"basePriceCents" INTEGER NOT NULL,
|
||||
"phaseDiscountCents" INTEGER NOT NULL,
|
||||
"voucherDiscountCents" INTEGER NOT NULL,
|
||||
"totalCents" INTEGER NOT NULL,
|
||||
"planType" "PlanType" NOT NULL,
|
||||
"priceSnapshot" JSONB NOT NULL,
|
||||
"priceLockedUntil" TIMESTAMP(3) NOT NULL,
|
||||
"voucherId" TEXT,
|
||||
"stripeCustomerId" TEXT,
|
||||
"stripeCheckoutSessionId" TEXT,
|
||||
"stripeCheckoutUrl" TEXT,
|
||||
"stripePaymentMethodId" TEXT,
|
||||
"paymentMethodDetachedAt" TIMESTAMP(3),
|
||||
"paidCents" INTEGER NOT NULL DEFAULT 0,
|
||||
"refundedCents" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"confirmedAt" TIMESTAMP(3),
|
||||
"canceledAt" TIMESTAMP(3),
|
||||
"cancelReason" TEXT,
|
||||
"reviewReason" TEXT,
|
||||
|
||||
CONSTRAINT "Booking_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Installment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"seq" INTEGER NOT NULL,
|
||||
"amountCents" INTEGER NOT NULL,
|
||||
"dueAt" TIMESTAMP(3),
|
||||
"status" "InstallmentStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"attemptCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastAttemptAt" TIMESTAMP(3),
|
||||
"nextAttemptAt" TIMESTAMP(3),
|
||||
"failureCode" TEXT,
|
||||
"paidAt" TIMESTAMP(3),
|
||||
"refundedCents" INTEGER NOT NULL DEFAULT 0,
|
||||
"stripePaymentIntentId" TEXT,
|
||||
"stripeChargeId" TEXT,
|
||||
"checkoutSessionId" TEXT,
|
||||
"checkoutExpiresAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Installment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Refund" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"installmentId" TEXT,
|
||||
"stripeRefundId" TEXT NOT NULL,
|
||||
"amountCents" INTEGER NOT NULL,
|
||||
"status" TEXT NOT NULL,
|
||||
"reason" TEXT,
|
||||
"createdBy" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Refund_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SeatReservation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"campId" TEXT NOT NULL,
|
||||
"livemode" BOOLEAN NOT NULL,
|
||||
"status" "ReservationStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"releasedAt" TIMESTAMP(3),
|
||||
"releaseReason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SeatReservation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Voucher" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"validFrom" TIMESTAMP(3),
|
||||
"validUntil" TIMESTAMP(3),
|
||||
"maxRedemptions" INTEGER NOT NULL DEFAULT 1,
|
||||
"boundEmail" TEXT,
|
||||
"internalNote" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Voucher_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VoucherRedemption" (
|
||||
"id" TEXT NOT NULL,
|
||||
"voucherId" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"livemode" BOOLEAN NOT NULL,
|
||||
"status" "RedemptionStatus" NOT NULL DEFAULT 'RESERVED',
|
||||
"releasedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "VoucherRedemption_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StripeEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"livemode" BOOLEAN NOT NULL,
|
||||
"status" "StripeEventStatus" NOT NULL DEFAULT 'PROCESSING',
|
||||
"error" TEXT,
|
||||
"attempts" INTEGER NOT NULL DEFAULT 1,
|
||||
"receivedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"processedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "StripeEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmailMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"dedupeKey" TEXT NOT NULL,
|
||||
"bookingId" TEXT,
|
||||
"toAddress" TEXT NOT NULL,
|
||||
"subject" TEXT NOT NULL,
|
||||
"textBody" TEXT NOT NULL,
|
||||
"htmlBody" TEXT NOT NULL,
|
||||
"status" "EmailStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"attempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastError" TEXT,
|
||||
"sentAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "EmailMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AuditLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"actorType" "ActorType" NOT NULL,
|
||||
"actorId" TEXT,
|
||||
"action" TEXT NOT NULL,
|
||||
"entityType" TEXT NOT NULL,
|
||||
"entityId" TEXT NOT NULL,
|
||||
"details" JSONB,
|
||||
|
||||
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RateLimitBucket" (
|
||||
"key" TEXT NOT NULL,
|
||||
"windowStart" TIMESTAMP(3) NOT NULL,
|
||||
"count" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "RateLimitBucket_pkey" PRIMARY KEY ("key")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"image" TEXT,
|
||||
"twoFactorEnabled" BOOLEAN DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "user_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"accountId" TEXT NOT NULL,
|
||||
"providerId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"accessToken" TEXT,
|
||||
"refreshToken" TEXT,
|
||||
"idToken" TEXT,
|
||||
"accessTokenExpiresAt" TIMESTAMP(3),
|
||||
"refreshTokenExpiresAt" TIMESTAMP(3),
|
||||
"scope" TEXT,
|
||||
"password" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "verification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "verification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "twoFactor" (
|
||||
"id" TEXT NOT NULL,
|
||||
"secret" TEXT NOT NULL,
|
||||
"backupCodes" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "twoFactor_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "rateLimit" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"count" INTEGER NOT NULL,
|
||||
"lastRequest" BIGINT NOT NULL,
|
||||
|
||||
CONSTRAINT "rateLimit_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Booking_reference_key" ON "Booking"("reference");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Booking_idempotencyKey_key" ON "Booking"("idempotencyKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Booking_statusTokenHash_key" ON "Booking"("statusTokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Booking_stripeCheckoutSessionId_key" ON "Booking"("stripeCheckoutSessionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_livemode_status_idx" ON "Booking"("livemode", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_email_idx" ON "Booking"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Booking_createdAt_idx" ON "Booking"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Installment_stripePaymentIntentId_key" ON "Installment"("stripePaymentIntentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Installment_stripeChargeId_key" ON "Installment"("stripeChargeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Installment_checkoutSessionId_key" ON "Installment"("checkoutSessionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Installment_status_dueAt_idx" ON "Installment"("status", "dueAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Installment_bookingId_seq_key" ON "Installment"("bookingId", "seq");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Refund_stripeRefundId_key" ON "Refund"("stripeRefundId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "SeatReservation_bookingId_key" ON "SeatReservation"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SeatReservation_campId_livemode_status_expiresAt_idx" ON "SeatReservation"("campId", "livemode", "status", "expiresAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Voucher_code_key" ON "Voucher"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VoucherRedemption_bookingId_key" ON "VoucherRedemption"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VoucherRedemption_voucherId_livemode_status_idx" ON "VoucherRedemption"("voucherId", "livemode", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "EmailMessage_dedupeKey_key" ON "EmailMessage"("dedupeKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmailMessage_status_createdAt_idx" ON "EmailMessage"("status", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_entityType_entityId_idx" ON "AuditLog"("entityType", "entityId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_email_key" ON "user"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "session_token_key" ON "session"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "session_userId_idx" ON "session"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "account_userId_idx" ON "account"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "verification_identifier_idx" ON "verification"("identifier");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "twoFactor_secret_idx" ON "twoFactor"("secret");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "twoFactor_userId_idx" ON "twoFactor"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "rateLimit_key_key" ON "rateLimit"("key");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_campId_fkey" FOREIGN KEY ("campId") REFERENCES "Camp"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_voucherId_fkey" FOREIGN KEY ("voucherId") REFERENCES "Voucher"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Installment" ADD CONSTRAINT "Installment_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Refund" ADD CONSTRAINT "Refund_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Refund" ADD CONSTRAINT "Refund_installmentId_fkey" FOREIGN KEY ("installmentId") REFERENCES "Installment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SeatReservation" ADD CONSTRAINT "SeatReservation_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SeatReservation" ADD CONSTRAINT "SeatReservation_campId_fkey" FOREIGN KEY ("campId") REFERENCES "Camp"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VoucherRedemption" ADD CONSTRAINT "VoucherRedemption_voucherId_fkey" FOREIGN KEY ("voucherId") REFERENCES "Voucher"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VoucherRedemption" ADD CONSTRAINT "VoucherRedemption_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmailMessage" ADD CONSTRAINT "EmailMessage_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "Booking"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "twoFactor" ADD CONSTRAINT "twoFactor_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Unveraenderlicher Preis-Snapshot und Zahlungsplan (Briefing Abschnitt 9).
|
||||
-- Der bei Buchung vereinbarte Preis darf auch durch einen Programmfehler oder
|
||||
-- einen direkten Datenbankzugriff nicht nachtraeglich veraendert werden.
|
||||
-- Eine Vertragsaenderung heisst: stornieren und neu buchen.
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE OR REPLACE FUNCTION booking_snapshot_immutable() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF NEW."reference" IS DISTINCT FROM OLD."reference"
|
||||
OR NEW."livemode" IS DISTINCT FROM OLD."livemode"
|
||||
OR NEW."idempotencyKey" IS DISTINCT FROM OLD."idempotencyKey"
|
||||
OR NEW."pricePhase" IS DISTINCT FROM OLD."pricePhase"
|
||||
OR NEW."basePriceCents" IS DISTINCT FROM OLD."basePriceCents"
|
||||
OR NEW."phaseDiscountCents" IS DISTINCT FROM OLD."phaseDiscountCents"
|
||||
OR NEW."voucherDiscountCents" IS DISTINCT FROM OLD."voucherDiscountCents"
|
||||
OR NEW."totalCents" IS DISTINCT FROM OLD."totalCents"
|
||||
OR NEW."planType" IS DISTINCT FROM OLD."planType"
|
||||
OR NEW."priceSnapshot" IS DISTINCT FROM OLD."priceSnapshot"
|
||||
OR NEW."priceLockedUntil" IS DISTINCT FROM OLD."priceLockedUntil"
|
||||
OR NEW."voucherId" IS DISTINCT FROM OLD."voucherId"
|
||||
THEN
|
||||
RAISE EXCEPTION 'Preis-Snapshot der Buchung % ist unveraenderlich', OLD."id";
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER booking_snapshot_immutable
|
||||
BEFORE UPDATE ON "Booking"
|
||||
FOR EACH ROW EXECUTE FUNCTION booking_snapshot_immutable();
|
||||
|
||||
CREATE OR REPLACE FUNCTION installment_plan_immutable() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF NEW."bookingId" IS DISTINCT FROM OLD."bookingId"
|
||||
OR NEW."seq" IS DISTINCT FROM OLD."seq"
|
||||
OR NEW."amountCents" IS DISTINCT FROM OLD."amountCents"
|
||||
OR NEW."dueAt" IS DISTINCT FROM OLD."dueAt"
|
||||
THEN
|
||||
RAISE EXCEPTION 'Rate % ist Teil des vereinbarten Zahlungsplans und unveraenderlich', OLD."id";
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER installment_plan_immutable
|
||||
BEFORE UPDATE ON "Installment"
|
||||
FOR EACH ROW EXECUTE FUNCTION installment_plan_immutable();
|
||||
|
||||
-- Plausibilitaet auf Datenbankebene.
|
||||
ALTER TABLE "Booking" ADD CONSTRAINT "Booking_amounts_check"
|
||||
CHECK ("totalCents" > 0 AND "basePriceCents" > 0 AND "phaseDiscountCents" >= 0
|
||||
AND "voucherDiscountCents" >= 0 AND "paidCents" >= 0 AND "refundedCents" >= 0
|
||||
AND "totalCents" = "basePriceCents" - "phaseDiscountCents" - "voucherDiscountCents");
|
||||
ALTER TABLE "Installment" ADD CONSTRAINT "Installment_amount_check"
|
||||
CHECK ("amountCents" > 0 AND "seq" BETWEEN 1 AND 3 AND "refundedCents" >= 0);
|
||||
ALTER TABLE "Camp" ADD CONSTRAINT "Camp_limits_check"
|
||||
CHECK ("capacity" >= 0 AND "stammmodelQuota" >= 0 AND "reservationMinutes" BETWEEN 30 AND 1440
|
||||
AND "finalInstallmentLeadDays" >= 0 AND "minInstallmentGapDays" >= 1);
|
||||
ALTER TABLE "Voucher" ADD CONSTRAINT "Voucher_limits_check"
|
||||
CHECK ("maxRedemptions" >= 1 AND "code" = upper("code"));
|
||||
@@ -0,0 +1,4 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "twoFactor" ADD COLUMN "failedVerificationCount" INTEGER DEFAULT 0,
|
||||
ADD COLUMN "lockedUntil" TIMESTAMP(3),
|
||||
ADD COLUMN "verified" BOOLEAN DEFAULT true;
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,529 @@
|
||||
// ARTISTA MODELCAMP 2027 — Datenmodell
|
||||
//
|
||||
// Geld: ausschliesslich ganzzahlige Cent-Betraege (Int), Waehrung immer EUR.
|
||||
// Zeit: Zeitstempel in UTC; fachliche Grenzen (Preisphasen, Faelligkeiten)
|
||||
// werden in src/lib/time.ts gegen Europe/Berlin berechnet.
|
||||
//
|
||||
// Test- und Livebuchungen liegen in denselben Tabellen, sind aber ueber
|
||||
// "livemode" strikt getrennt: Kapazitaet und Gutscheinkontingente zaehlen nur
|
||||
// Buchungen desselben Modus, damit Stripe-Testbuchungen nie echte Plaetze
|
||||
// blockieren.
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Camp und Verkaufsfreigaben
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enum PricePhase {
|
||||
PREVIEW
|
||||
EARLY_BIRD
|
||||
REGULAR
|
||||
}
|
||||
|
||||
/// Genau eine Zeile (id = "modelcamp-2027"). Enthaelt die betrieblichen
|
||||
/// Angaben, die den Live-Verkauf freischalten. Die Preise selbst sind
|
||||
/// verbindlich vorgegeben und stehen in src/lib/pricing.ts, nicht hier.
|
||||
model Camp {
|
||||
id String @id
|
||||
|
||||
/// Hauptschalter fuer den Live-Verkauf. Wirkt nur zusammen mit einer
|
||||
/// vollstaendigen Freigabe-Checkliste (src/lib/sales-gate.ts).
|
||||
salesEnabled Boolean @default(false)
|
||||
|
||||
capacity Int @default(10)
|
||||
capacityConfirmed Boolean @default(false)
|
||||
stammmodelQuota Int @default(4)
|
||||
stammmodelQuotaConfirmed Boolean @default(false)
|
||||
|
||||
campStartDate DateTime? @db.Date
|
||||
campEndDate DateTime? @db.Date
|
||||
dateConfirmed Boolean @default(false)
|
||||
|
||||
locationName String?
|
||||
locationRegion String?
|
||||
locationConfirmed Boolean @default(false)
|
||||
|
||||
/// Verbindliche Zimmer- und Bettbeschreibung, wird vor dem Kauf angezeigt
|
||||
/// und muss ausdruecklich bestaetigt werden.
|
||||
accommodationText String?
|
||||
accommodationConfirmed Boolean @default(false)
|
||||
|
||||
/// Bestaetigt, dass die in src/content/site.ts als "confirmed" markierten
|
||||
/// Leistungen verbindlich sind.
|
||||
servicesConfirmed Boolean @default(false)
|
||||
|
||||
organizerName String?
|
||||
organizerAddress String?
|
||||
organizerEmail String?
|
||||
organizerPhone String?
|
||||
organizerConfirmed Boolean @default(false)
|
||||
|
||||
/// Steuerlich gepruefter Preis-Hinweis, z. B. wie die Umsatzsteuer
|
||||
/// ausgewiesen wird. Ohne Freigabe erscheint KEINE Steueraussage.
|
||||
taxNote String?
|
||||
taxConfirmed Boolean @default(false)
|
||||
|
||||
/// Version der freigegebenen Buchungs-, Storno- und Zahlungsbedingungen.
|
||||
termsVersion String?
|
||||
termsUrl String?
|
||||
termsApproved Boolean @default(false)
|
||||
|
||||
travelLawChecked Boolean @default(false)
|
||||
|
||||
installmentsApproved Boolean @default(false)
|
||||
installmentRuleApproved Boolean @default(false)
|
||||
|
||||
/// Reservierungsdauer fuer einen begonnenen Checkout. Stripe verlangt fuer
|
||||
/// expires_at mindestens 30 Minuten.
|
||||
reservationMinutes Int @default(30)
|
||||
finalInstallmentLeadDays Int @default(30)
|
||||
minInstallmentGapDays Int @default(14)
|
||||
|
||||
/// Nur im Stripe-Testmodus wirksam: simuliert eine Preisphase, damit
|
||||
/// Testbuchungen auch vor dem 01.11.2026 moeglich sind.
|
||||
testPhaseOverride PricePhase?
|
||||
|
||||
updatedAt DateTime @updatedAt
|
||||
updatedBy String?
|
||||
|
||||
bookings Booking[]
|
||||
reservations SeatReservation[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Buchung
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enum BookingStatus {
|
||||
/// Platz reserviert, Zahlung der ersten Rate steht aus.
|
||||
PENDING
|
||||
CONFIRMED
|
||||
CANCELED
|
||||
/// Checkout abgebrochen oder abgelaufen, Platz wieder frei.
|
||||
EXPIRED
|
||||
/// Zahlung eingegangen, aber ein Konflikt (z. B. Platz inzwischen
|
||||
/// vergeben) verlangt eine manuelle Entscheidung.
|
||||
NEEDS_REVIEW
|
||||
}
|
||||
|
||||
enum PaymentStatus {
|
||||
UNPAID
|
||||
/// Verzoegerte Zahlungsart (z. B. SEPA) ist unterwegs.
|
||||
PROCESSING
|
||||
PARTIALLY_PAID
|
||||
PAID
|
||||
FAILED
|
||||
PARTIALLY_REFUNDED
|
||||
REFUNDED
|
||||
}
|
||||
|
||||
enum PlanType {
|
||||
FULL
|
||||
TWO_INSTALLMENTS
|
||||
THREE_INSTALLMENTS
|
||||
}
|
||||
|
||||
model Booking {
|
||||
id String @id @default(cuid())
|
||||
/// Oeffentliche Buchungsnummer, z. B. AMC27-7K3Q9P.
|
||||
reference String @unique
|
||||
|
||||
campId String
|
||||
camp Camp @relation(fields: [campId], references: [id])
|
||||
|
||||
status BookingStatus @default(PENDING)
|
||||
paymentStatus PaymentStatus @default(UNPAID)
|
||||
livemode Boolean
|
||||
|
||||
/// Vom Browser erzeugter Schluessel je Buchungsversuch: ein doppelt
|
||||
/// abgeschicktes Formular fuehrt zur selben Buchung statt zu zweien.
|
||||
idempotencyKey String @unique
|
||||
/// SHA-256 des Zugangstokens fuer die Statusseite. Das Token selbst wird
|
||||
/// nie gespeichert.
|
||||
statusTokenHash String @unique
|
||||
|
||||
firstName String
|
||||
lastName String
|
||||
email String
|
||||
phone String
|
||||
country String
|
||||
notes String?
|
||||
|
||||
adultConfirmedAt DateTime
|
||||
termsAcceptedAt DateTime
|
||||
termsVersion String
|
||||
accommodationAcknowledgedAt DateTime
|
||||
privacyAcknowledgedAt DateTime
|
||||
/// Zustimmung zum Einzug spaeterer Raten (nur bei Ratenzahlung).
|
||||
installmentMandateAt DateTime?
|
||||
|
||||
// --- Preis-Snapshot (per Datenbank-Trigger unveraenderlich) -------------
|
||||
pricePhase PricePhase
|
||||
basePriceCents Int
|
||||
phaseDiscountCents Int
|
||||
voucherDiscountCents Int
|
||||
totalCents Int
|
||||
planType PlanType
|
||||
priceSnapshot Json
|
||||
/// Bis hierhin gilt der gebundene Preis (= Ablauf der Stripe-Session).
|
||||
priceLockedUntil DateTime
|
||||
|
||||
voucherId String?
|
||||
voucher Voucher? @relation(fields: [voucherId], references: [id])
|
||||
|
||||
stripeCustomerId String?
|
||||
stripeCheckoutSessionId String? @unique
|
||||
stripeCheckoutUrl String?
|
||||
/// Referenz (keine Kartendaten) auf die fuer Folgeraten gespeicherte
|
||||
/// Zahlungsmethode. Wird nach der letzten Rate bei Stripe getrennt.
|
||||
stripePaymentMethodId String?
|
||||
paymentMethodDetachedAt DateTime?
|
||||
|
||||
paidCents Int @default(0)
|
||||
refundedCents Int @default(0)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
confirmedAt DateTime?
|
||||
canceledAt DateTime?
|
||||
cancelReason String?
|
||||
reviewReason String?
|
||||
|
||||
installments Installment[]
|
||||
reservation SeatReservation?
|
||||
redemption VoucherRedemption?
|
||||
refunds Refund[]
|
||||
emails EmailMessage[]
|
||||
|
||||
@@index([livemode, status])
|
||||
@@index([email])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
enum InstallmentStatus {
|
||||
/// Noch nicht faellig bzw. erste Rate vor dem Checkout.
|
||||
PENDING
|
||||
/// Einzug laeuft (Worker hat die Rate beansprucht).
|
||||
PROCESSING
|
||||
PAID
|
||||
FAILED
|
||||
/// Bank verlangt zusaetzliche Authentifizierung: Kundin zahlt ueber einen
|
||||
/// Link auf der Statusseite selbst.
|
||||
REQUIRES_ACTION
|
||||
CANCELED
|
||||
}
|
||||
|
||||
model Installment {
|
||||
id String @id @default(cuid())
|
||||
bookingId String
|
||||
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Restrict)
|
||||
|
||||
seq Int
|
||||
amountCents Int
|
||||
/// null = faellig bei Buchung (erste Rate).
|
||||
dueAt DateTime?
|
||||
|
||||
status InstallmentStatus @default(PENDING)
|
||||
attemptCount Int @default(0)
|
||||
lastAttemptAt DateTime?
|
||||
nextAttemptAt DateTime?
|
||||
failureCode String?
|
||||
paidAt DateTime?
|
||||
refundedCents Int @default(0)
|
||||
|
||||
stripePaymentIntentId String? @unique
|
||||
stripeChargeId String? @unique
|
||||
/// Offene Checkout-Session, ueber die die Kundin diese Rate selbst zahlt.
|
||||
checkoutSessionId String? @unique
|
||||
checkoutExpiresAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
refunds Refund[]
|
||||
|
||||
@@unique([bookingId, seq])
|
||||
@@index([status, dueAt])
|
||||
}
|
||||
|
||||
model Refund {
|
||||
id String @id @default(cuid())
|
||||
bookingId String
|
||||
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Restrict)
|
||||
installmentId String?
|
||||
installment Installment? @relation(fields: [installmentId], references: [id])
|
||||
|
||||
stripeRefundId String @unique
|
||||
amountCents Int
|
||||
status String
|
||||
reason String?
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kapazitaet
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enum ReservationStatus {
|
||||
/// Zeitlich begrenzt, verfaellt mit expiresAt.
|
||||
ACTIVE
|
||||
/// Verzoegerte Zahlung unterwegs: Platz bleibt bis zum Ergebnis gehalten.
|
||||
HELD
|
||||
CONVERTED
|
||||
RELEASED
|
||||
}
|
||||
|
||||
model SeatReservation {
|
||||
id String @id @default(cuid())
|
||||
bookingId String @unique
|
||||
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Restrict)
|
||||
campId String
|
||||
camp Camp @relation(fields: [campId], references: [id])
|
||||
livemode Boolean
|
||||
|
||||
status ReservationStatus @default(ACTIVE)
|
||||
expiresAt DateTime
|
||||
releasedAt DateTime?
|
||||
releaseReason String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([campId, livemode, status, expiresAt])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gutscheine (Stammmodel-Codes)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
model Voucher {
|
||||
id String @id @default(cuid())
|
||||
/// Immer in Grossbuchstaben normalisiert gespeichert.
|
||||
code String @unique
|
||||
|
||||
active Boolean @default(true)
|
||||
validFrom DateTime?
|
||||
validUntil DateTime?
|
||||
maxRedemptions Int @default(1)
|
||||
/// Optional: nur fuer diese E-Mail-Adresse (klein geschrieben) einloesbar.
|
||||
boundEmail String?
|
||||
/// Interne Zuordnung, z. B. Name des Stammmodels. Nie oeffentlich.
|
||||
internalNote String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
redemptions VoucherRedemption[]
|
||||
bookings Booking[]
|
||||
}
|
||||
|
||||
enum RedemptionStatus {
|
||||
RESERVED
|
||||
COMPLETED
|
||||
RELEASED
|
||||
}
|
||||
|
||||
model VoucherRedemption {
|
||||
id String @id @default(cuid())
|
||||
voucherId String
|
||||
voucher Voucher @relation(fields: [voucherId], references: [id], onDelete: Restrict)
|
||||
bookingId String @unique
|
||||
booking Booking @relation(fields: [bookingId], references: [id], onDelete: Restrict)
|
||||
livemode Boolean
|
||||
|
||||
status RedemptionStatus @default(RESERVED)
|
||||
releasedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([voucherId, livemode, status])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stripe-Ereignisse, E-Mail-Ausgang, Protokoll, Rate Limits
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enum StripeEventStatus {
|
||||
PROCESSING
|
||||
PROCESSED
|
||||
IGNORED
|
||||
FAILED
|
||||
}
|
||||
|
||||
model StripeEvent {
|
||||
/// Stripe-Event-ID (evt_...) — Primaerschluessel macht die Verarbeitung
|
||||
/// idempotent.
|
||||
id String @id
|
||||
type String
|
||||
livemode Boolean
|
||||
status StripeEventStatus @default(PROCESSING)
|
||||
error String?
|
||||
attempts Int @default(1)
|
||||
receivedAt DateTime @default(now())
|
||||
processedAt DateTime?
|
||||
}
|
||||
|
||||
enum EmailStatus {
|
||||
PENDING
|
||||
SENT
|
||||
FAILED
|
||||
}
|
||||
|
||||
/// Transaktionaler Postausgang: E-Mails entstehen in derselben Transaktion
|
||||
/// wie die Statusaenderung und werden danach (bzw. vom Worker) versendet.
|
||||
model EmailMessage {
|
||||
id String @id @default(cuid())
|
||||
/// Verhindert doppelte Mails bei wiederholten Webhooks.
|
||||
dedupeKey String @unique
|
||||
bookingId String?
|
||||
booking Booking? @relation(fields: [bookingId], references: [id])
|
||||
|
||||
toAddress String
|
||||
subject String
|
||||
textBody String
|
||||
htmlBody String
|
||||
|
||||
status EmailStatus @default(PENDING)
|
||||
attempts Int @default(0)
|
||||
lastError String?
|
||||
sentAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status, createdAt])
|
||||
}
|
||||
|
||||
enum ActorType {
|
||||
ADMIN
|
||||
CUSTOMER
|
||||
STRIPE
|
||||
WORKER
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
/// Nachvollziehbarkeit fuer Bestands-, Buchungs- und Zahlungsaktionen.
|
||||
model AuditLog {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
actorType ActorType
|
||||
actorId String?
|
||||
action String
|
||||
entityType String
|
||||
entityId String
|
||||
details Json?
|
||||
|
||||
@@index([entityType, entityId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
/// Gemeinsamer Zaehler fuer Rate Limits — funktioniert ueber mehrere
|
||||
/// Replikate hinweg. Der Schluessel enthaelt nur einen Hash der IP.
|
||||
model RateLimitBucket {
|
||||
key String @id
|
||||
windowStart DateTime
|
||||
count Int
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Better Auth (Adminbereich)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
model User {
|
||||
id String @id
|
||||
name String
|
||||
email String @unique
|
||||
emailVerified Boolean @default(false)
|
||||
image String?
|
||||
twoFactorEnabled Boolean? @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
twofactors TwoFactor[]
|
||||
|
||||
@@map("user")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id
|
||||
expiresAt DateTime
|
||||
token String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@map("session")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id
|
||||
accountId String
|
||||
providerId String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
accessToken String?
|
||||
refreshToken String?
|
||||
idToken String?
|
||||
accessTokenExpiresAt DateTime?
|
||||
refreshTokenExpiresAt DateTime?
|
||||
scope String?
|
||||
password String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@map("account")
|
||||
}
|
||||
|
||||
model Verification {
|
||||
id String @id
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([identifier])
|
||||
@@map("verification")
|
||||
}
|
||||
|
||||
model TwoFactor {
|
||||
id String @id
|
||||
secret String
|
||||
backupCodes String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
verified Boolean? @default(true)
|
||||
failedVerificationCount Int? @default(0)
|
||||
lockedUntil DateTime?
|
||||
|
||||
@@index([secret])
|
||||
@@index([userId])
|
||||
@@map("twoFactor")
|
||||
}
|
||||
|
||||
/// Rate Limits der Anmeldung (Better Auth, Modellname vom Adapter vorgegeben).
|
||||
model RateLimit {
|
||||
id String @id
|
||||
key String @unique
|
||||
count Int
|
||||
lastRequest BigInt
|
||||
|
||||
@@map("rateLimit")
|
||||
}
|
||||
Reference in new issue
Block a user