Initial commit
This commit is contained in:
34
apps/backend/prisma/complex.prisma
Normal file
34
apps/backend/prisma/complex.prisma
Normal file
@@ -0,0 +1,34 @@
|
||||
model Complex {
|
||||
id String @id @db.Uuid
|
||||
complexName String @map("complex_name")
|
||||
physicalAddress String? @map("physical_address") @db.VarChar(200)
|
||||
complexSlug String @unique @map("complex_slug")
|
||||
adminEmail String @map("admin_email")
|
||||
planCode String? @map("plan_code") @db.VarChar(10)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
plan Plan? @relation(fields: [planCode], references: [code])
|
||||
users ComplexUser[]
|
||||
courts Court[]
|
||||
|
||||
@@index([planCode])
|
||||
@@index([complexSlug])
|
||||
@@map("complexes")
|
||||
}
|
||||
|
||||
enum ComplexUserRole {
|
||||
ADMIN
|
||||
}
|
||||
|
||||
model ComplexUser {
|
||||
complexId String @map("complex_id") @db.Uuid
|
||||
userId String @map("user_id") @db.Uuid
|
||||
role ComplexUserRole @default(ADMIN)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([complexId, userId])
|
||||
@@index([userId])
|
||||
@@map("complex_users")
|
||||
}
|
||||
95
apps/backend/prisma/court.prisma
Normal file
95
apps/backend/prisma/court.prisma
Normal file
@@ -0,0 +1,95 @@
|
||||
enum DayOfWeek {
|
||||
MONDAY
|
||||
TUESDAY
|
||||
WEDNESDAY
|
||||
THURSDAY
|
||||
FRIDAY
|
||||
SATURDAY
|
||||
SUNDAY
|
||||
}
|
||||
|
||||
enum CourtBookingStatus {
|
||||
CONFIRMED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
model Sport {
|
||||
id String @id @db.Uuid
|
||||
name String @unique
|
||||
slug String @unique
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
courts Court[]
|
||||
|
||||
@@map("sports")
|
||||
}
|
||||
|
||||
model Court {
|
||||
id String @id @db.Uuid
|
||||
complexId String @map("complex_id") @db.Uuid
|
||||
sportId String @map("sport_id") @db.Uuid
|
||||
name String
|
||||
slotDurationMinutes Int @map("slot_duration_minutes")
|
||||
basePrice Decimal @map("base_price") @db.Decimal(19, 2)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)
|
||||
sport Sport @relation(fields: [sportId], references: [id])
|
||||
availabilities CourtAvailability[]
|
||||
priceRules CourtPriceRule[]
|
||||
bookings CourtBooking[]
|
||||
|
||||
@@index([complexId])
|
||||
@@index([sportId])
|
||||
@@map("courts")
|
||||
}
|
||||
|
||||
model CourtAvailability {
|
||||
id String @id @db.Uuid
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
dayOfWeek DayOfWeek @map("day_of_week")
|
||||
startTime String @map("start_time") @db.VarChar(5)
|
||||
endTime String @map("end_time") @db.VarChar(5)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([courtId, dayOfWeek])
|
||||
@@map("court_availabilities")
|
||||
}
|
||||
|
||||
model CourtPriceRule {
|
||||
id String @id @db.Uuid
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
dayOfWeek DayOfWeek? @map("day_of_week")
|
||||
startTime String? @map("start_time") @db.VarChar(5)
|
||||
endTime String? @map("end_time") @db.VarChar(5)
|
||||
price Decimal @db.Decimal(19, 2)
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([courtId, isActive])
|
||||
@@map("court_price_rules")
|
||||
}
|
||||
|
||||
model CourtBooking {
|
||||
id String @id @db.Uuid
|
||||
bookingCode String @unique @map("booking_code") @db.VarChar(8)
|
||||
courtId String @map("court_id") @db.Uuid
|
||||
bookingDate DateTime @map("booking_date") @db.Date
|
||||
startTime String @map("start_time") @db.VarChar(5)
|
||||
endTime String @map("end_time") @db.VarChar(5)
|
||||
customerName String @map("customer_name") @db.VarChar(120)
|
||||
customerPhone String @map("customer_phone") @db.VarChar(30)
|
||||
status CourtBookingStatus @default(CONFIRMED)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
court Court @relation(fields: [courtId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([courtId, bookingDate, startTime])
|
||||
@@index([courtId, bookingDate])
|
||||
@@index([bookingDate])
|
||||
@@map("court_bookings")
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "plans" (
|
||||
"code" VARCHAR(10) NOT NULL,
|
||||
"name" VARCHAR(30) NOT NULL,
|
||||
"price" DECIMAL(19,2) NOT NULL,
|
||||
"rules" JSONB NOT NULL,
|
||||
"last_updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "plans_pkey" PRIMARY KEY ("code")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "complexes" (
|
||||
"id" UUID NOT NULL,
|
||||
"complex_name" TEXT NOT NULL,
|
||||
"complex_slug" TEXT NOT NULL,
|
||||
"admin_email" TEXT NOT NULL,
|
||||
"plan_code" VARCHAR(10),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "complexes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "complexes_complex_slug_key" ON "complexes"("complex_slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "complexes_plan_code_idx" ON "complexes"("plan_code");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "complexes" ADD CONSTRAINT "complexes_plan_code_fkey" FOREIGN KEY ("plan_code") REFERENCES "plans"("code") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,64 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ComplexUserRole" AS ENUM ('ADMIN');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "complex_users" (
|
||||
"complex_id" UUID NOT NULL,
|
||||
"user_id" UUID NOT NULL,
|
||||
"role" "ComplexUserRole" NOT NULL DEFAULT 'ADMIN',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "complex_users_pkey" PRIMARY KEY ("complex_id","user_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "onboarding_requests" (
|
||||
"id" UUID NOT NULL,
|
||||
"full_name" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"email_verification_token_hash" TEXT NOT NULL,
|
||||
"email_verification_sent_at" TIMESTAMP(3) NOT NULL,
|
||||
"email_verified_at" TIMESTAMP(3),
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"completed_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "onboarding_requests_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" UUID NOT NULL,
|
||||
"supabase_user_id" UUID NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"full_name" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "complex_users_user_id_idx" ON "complex_users"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "onboarding_requests_email_verification_token_hash_key" ON "onboarding_requests"("email_verification_token_hash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "onboarding_requests_email_idx" ON "onboarding_requests"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "onboarding_requests_expires_at_idx" ON "onboarding_requests"("expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_supabase_user_id_key" ON "users"("supabase_user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "complex_users" ADD CONSTRAINT "complex_users_complex_id_fkey" FOREIGN KEY ("complex_id") REFERENCES "complexes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "complex_users" ADD CONSTRAINT "complex_users_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,35 @@
|
||||
-- DropIndex
|
||||
DROP INDEX IF EXISTS "onboarding_requests_email_verification_token_hash_key";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX IF EXISTS "onboarding_requests_expires_at_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "onboarding_requests"
|
||||
ADD COLUMN "otp_hash" TEXT,
|
||||
ADD COLUMN "otp_expires_at" TIMESTAMP(3),
|
||||
ADD COLUMN "otp_attempts" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "otp_last_sent_at" TIMESTAMP(3),
|
||||
ADD COLUMN "otp_resend_count" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Backfill for existing rows
|
||||
UPDATE "onboarding_requests"
|
||||
SET
|
||||
"otp_hash" = md5("id"::text || ':legacy'),
|
||||
"otp_expires_at" = COALESCE("expires_at", NOW()),
|
||||
"otp_last_sent_at" = COALESCE("email_verification_sent_at", NOW());
|
||||
|
||||
-- Enforce required fields
|
||||
ALTER TABLE "onboarding_requests"
|
||||
ALTER COLUMN "otp_hash" SET NOT NULL,
|
||||
ALTER COLUMN "otp_expires_at" SET NOT NULL,
|
||||
ALTER COLUMN "otp_last_sent_at" SET NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "onboarding_requests_otp_expires_at_idx" ON "onboarding_requests"("otp_expires_at");
|
||||
|
||||
-- Drop legacy columns
|
||||
ALTER TABLE "onboarding_requests"
|
||||
DROP COLUMN "email_verification_token_hash",
|
||||
DROP COLUMN "email_verification_sent_at",
|
||||
DROP COLUMN "expires_at";
|
||||
@@ -0,0 +1,93 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DayOfWeek" AS ENUM (
|
||||
'MONDAY',
|
||||
'TUESDAY',
|
||||
'WEDNESDAY',
|
||||
'THURSDAY',
|
||||
'FRIDAY',
|
||||
'SATURDAY',
|
||||
'SUNDAY'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "sports" (
|
||||
"id" UUID NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "sports_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "courts" (
|
||||
"id" UUID NOT NULL,
|
||||
"complex_id" UUID NOT NULL,
|
||||
"sport_id" UUID NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slot_duration_minutes" INTEGER NOT NULL,
|
||||
"base_price" DECIMAL(19,2) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "courts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "court_availabilities" (
|
||||
"id" UUID NOT NULL,
|
||||
"court_id" UUID NOT NULL,
|
||||
"day_of_week" "DayOfWeek" NOT NULL,
|
||||
"start_time" VARCHAR(5) NOT NULL,
|
||||
"end_time" VARCHAR(5) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "court_availabilities_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "court_price_rules" (
|
||||
"id" UUID NOT NULL,
|
||||
"court_id" UUID NOT NULL,
|
||||
"day_of_week" "DayOfWeek",
|
||||
"start_time" VARCHAR(5),
|
||||
"end_time" VARCHAR(5),
|
||||
"price" DECIMAL(19,2) NOT NULL,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "court_price_rules_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sports_name_key" ON "sports"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "sports_slug_key" ON "sports"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "courts_complex_id_idx" ON "courts"("complex_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "courts_sport_id_idx" ON "courts"("sport_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "court_availabilities_court_id_day_of_week_idx" ON "court_availabilities"("court_id", "day_of_week");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "court_price_rules_court_id_is_active_idx" ON "court_price_rules"("court_id", "is_active");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "courts" ADD CONSTRAINT "courts_complex_id_fkey" FOREIGN KEY ("complex_id") REFERENCES "complexes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "courts" ADD CONSTRAINT "courts_sport_id_fkey" FOREIGN KEY ("sport_id") REFERENCES "sports"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "court_availabilities" ADD CONSTRAINT "court_availabilities_court_id_fkey" FOREIGN KEY ("court_id") REFERENCES "courts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "court_price_rules" ADD CONSTRAINT "court_price_rules_court_id_fkey" FOREIGN KEY ("court_id") REFERENCES "courts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CourtBookingStatus" AS ENUM ('CONFIRMED', 'CANCELLED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "court_bookings" (
|
||||
"id" UUID NOT NULL,
|
||||
"court_id" UUID NOT NULL,
|
||||
"booking_date" DATE NOT NULL,
|
||||
"start_time" VARCHAR(5) NOT NULL,
|
||||
"end_time" VARCHAR(5) NOT NULL,
|
||||
"customer_name" VARCHAR(120) NOT NULL,
|
||||
"customer_phone" VARCHAR(30) NOT NULL,
|
||||
"status" "CourtBookingStatus" NOT NULL DEFAULT 'CONFIRMED',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "court_bookings_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "court_bookings_court_id_booking_date_start_time_key" ON "court_bookings"("court_id", "booking_date", "start_time");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "court_bookings_court_id_booking_date_idx" ON "court_bookings"("court_id", "booking_date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "court_bookings_booking_date_idx" ON "court_bookings"("booking_date");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "court_bookings" ADD CONSTRAINT "court_bookings_court_id_fkey" FOREIGN KEY ("court_id") REFERENCES "courts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "court_bookings" ADD COLUMN "booking_code" VARCHAR(8);
|
||||
|
||||
-- Backfill existing rows
|
||||
UPDATE "court_bookings"
|
||||
SET "booking_code" = UPPER(SUBSTRING(REPLACE("id"::text, '-', '') FROM 1 FOR 8))
|
||||
WHERE "booking_code" IS NULL;
|
||||
|
||||
-- Enforce not-null and uniqueness
|
||||
ALTER TABLE "court_bookings" ALTER COLUMN "booking_code" SET NOT NULL;
|
||||
CREATE UNIQUE INDEX "court_bookings_booking_code_key" ON "court_bookings"("booking_code");
|
||||
@@ -0,0 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX "complexes_complex_slug_idx" ON "complexes"("complex_slug");
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "complexes"
|
||||
ADD COLUMN "physical_address" VARCHAR(200);
|
||||
3
apps/backend/prisma/migrations/migration_lock.toml
Normal file
3
apps/backend/prisma/migrations/migration_lock.toml
Normal file
@@ -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"
|
||||
18
apps/backend/prisma/onboarding.prisma
Normal file
18
apps/backend/prisma/onboarding.prisma
Normal file
@@ -0,0 +1,18 @@
|
||||
model OnboardingRequest {
|
||||
id String @id @db.Uuid
|
||||
fullName String @map("full_name")
|
||||
email String
|
||||
otpHash String @map("otp_hash")
|
||||
otpExpiresAt DateTime @map("otp_expires_at")
|
||||
otpAttempts Int @default(0) @map("otp_attempts")
|
||||
otpLastSentAt DateTime @map("otp_last_sent_at")
|
||||
otpResendCount Int @default(0) @map("otp_resend_count")
|
||||
emailVerifiedAt DateTime? @map("email_verified_at")
|
||||
completedAt DateTime? @map("completed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([email])
|
||||
@@index([otpExpiresAt])
|
||||
@@map("onboarding_requests")
|
||||
}
|
||||
10
apps/backend/prisma/plan.prisma
Normal file
10
apps/backend/prisma/plan.prisma
Normal file
@@ -0,0 +1,10 @@
|
||||
model Plan {
|
||||
code String @id @db.VarChar(10)
|
||||
name String @db.VarChar(30)
|
||||
price Decimal @db.Decimal(19, 2)
|
||||
rules Json
|
||||
lastUpdatedAt DateTime @default(now()) @map("last_updated_at")
|
||||
complexes Complex[]
|
||||
|
||||
@@map("plans")
|
||||
}
|
||||
77
apps/backend/prisma/plans.seed-data.ts
Normal file
77
apps/backend/prisma/plans.seed-data.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
export const planSeeds = [
|
||||
{
|
||||
code: 'BASIC',
|
||||
name: 'Basic',
|
||||
price: '49.00',
|
||||
rules: {
|
||||
version: 'v1',
|
||||
limits: {
|
||||
maxCourts: 2,
|
||||
maxBookingsPerDay: 80,
|
||||
maxActiveUsers: 3,
|
||||
maxConcurrentBookingsPerSlot: 1,
|
||||
},
|
||||
policies: {
|
||||
maxAdvanceBookingDays: 7,
|
||||
minCancellationNoticeHours: 2,
|
||||
allowOverbooking: false,
|
||||
},
|
||||
features: {
|
||||
onlinePayments: false,
|
||||
publicBookingPage: true,
|
||||
advancedReports: false,
|
||||
whatsappReminders: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'ADVANCED',
|
||||
name: 'Advanced',
|
||||
price: '99.00',
|
||||
rules: {
|
||||
version: 'v1',
|
||||
limits: {
|
||||
maxCourts: 5,
|
||||
maxBookingsPerDay: 220,
|
||||
maxActiveUsers: 8,
|
||||
maxConcurrentBookingsPerSlot: 1,
|
||||
},
|
||||
policies: {
|
||||
maxAdvanceBookingDays: 15,
|
||||
minCancellationNoticeHours: 2,
|
||||
allowOverbooking: false,
|
||||
},
|
||||
features: {
|
||||
onlinePayments: true,
|
||||
publicBookingPage: true,
|
||||
advancedReports: true,
|
||||
whatsappReminders: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'ENTERPRISE',
|
||||
name: 'Enterprise',
|
||||
price: '199.00',
|
||||
rules: {
|
||||
version: 'v1',
|
||||
limits: {
|
||||
maxCourts: 20,
|
||||
maxBookingsPerDay: 2000,
|
||||
maxActiveUsers: 50,
|
||||
maxConcurrentBookingsPerSlot: 2,
|
||||
},
|
||||
policies: {
|
||||
maxAdvanceBookingDays: 60,
|
||||
minCancellationNoticeHours: 1,
|
||||
allowOverbooking: true,
|
||||
},
|
||||
features: {
|
||||
onlinePayments: true,
|
||||
publicBookingPage: true,
|
||||
advancedReports: true,
|
||||
whatsappReminders: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
55
apps/backend/prisma/reset-plans.ts
Normal file
55
apps/backend/prisma/reset-plans.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'dotenv/config'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
import { PrismaClient } from '../src/generated/prisma/client'
|
||||
import { planSeeds } from './plans.seed-data'
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error('Missing DATABASE_URL in environment for plan reset.')
|
||||
}
|
||||
|
||||
const adapter = new PrismaPg({
|
||||
connectionString: databaseUrl,
|
||||
})
|
||||
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
|
||||
async function run() {
|
||||
const complexesUsingPlans = await prisma.complex.count({
|
||||
where: { planCode: { not: null } },
|
||||
})
|
||||
|
||||
if (complexesUsingPlans > 0) {
|
||||
throw new Error(
|
||||
`No se puede resetear planes: hay ${complexesUsingPlans} complejos con plan asignado.`,
|
||||
)
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.plan.deleteMany({})
|
||||
|
||||
for (const plan of planSeeds) {
|
||||
await tx.plan.create({
|
||||
data: {
|
||||
code: plan.code,
|
||||
name: plan.name,
|
||||
price: plan.price,
|
||||
rules: plan.rules,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Planes reseteados: BASIC, ADVANCED, ENTERPRISE')
|
||||
}
|
||||
|
||||
run()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
.catch(async (error) => {
|
||||
console.error(error)
|
||||
await prisma.$disconnect()
|
||||
process.exit(1)
|
||||
})
|
||||
8
apps/backend/prisma/schema.prisma
Normal file
8
apps/backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,8 @@
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
64
apps/backend/prisma/seed.ts
Normal file
64
apps/backend/prisma/seed.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'dotenv/config'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
import { v7 as uuidv7 } from 'uuid'
|
||||
import { PrismaClient } from '../src/generated/prisma/client'
|
||||
import { planSeeds } from './plans.seed-data'
|
||||
import { sportSeeds } from './sports.seed-data'
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error('Missing DATABASE_URL in environment for seeding.')
|
||||
}
|
||||
|
||||
const adapter = new PrismaPg({
|
||||
connectionString: databaseUrl,
|
||||
})
|
||||
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
|
||||
async function run() {
|
||||
for (const plan of planSeeds) {
|
||||
await prisma.plan.upsert({
|
||||
where: { code: plan.code },
|
||||
update: {
|
||||
name: plan.name,
|
||||
price: plan.price,
|
||||
rules: plan.rules,
|
||||
lastUpdatedAt: new Date(),
|
||||
},
|
||||
create: {
|
||||
code: plan.code,
|
||||
name: plan.name,
|
||||
price: plan.price,
|
||||
rules: plan.rules,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for (const sport of sportSeeds) {
|
||||
await prisma.sport.upsert({
|
||||
where: { slug: sport.slug },
|
||||
update: {
|
||||
name: sport.name,
|
||||
isActive: true,
|
||||
},
|
||||
create: {
|
||||
id: uuidv7(),
|
||||
name: sport.name,
|
||||
slug: sport.slug,
|
||||
isActive: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
.catch(async (error) => {
|
||||
console.error(error)
|
||||
await prisma.$disconnect()
|
||||
process.exit(1)
|
||||
})
|
||||
8
apps/backend/prisma/sports.seed-data.ts
Normal file
8
apps/backend/prisma/sports.seed-data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const sportSeeds = [
|
||||
{ name: 'Futbol 5', slug: 'futbol-5' },
|
||||
{ name: 'Padel', slug: 'padel' },
|
||||
{ name: 'Tenis', slug: 'tenis' },
|
||||
{ name: 'Pickleball', slug: 'pickleball' },
|
||||
{ name: 'Basquet', slug: 'basquet' },
|
||||
{ name: 'Basquet 3', slug: 'basquet-3' },
|
||||
] as const
|
||||
11
apps/backend/prisma/user.prisma
Normal file
11
apps/backend/prisma/user.prisma
Normal file
@@ -0,0 +1,11 @@
|
||||
model User {
|
||||
id String @id @db.Uuid
|
||||
supabaseUserId String @unique @map("supabase_user_id") @db.Uuid
|
||||
email String @unique
|
||||
fullName String @map("full_name")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
complexes ComplexUser[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
Reference in New Issue
Block a user