add first files

This commit is contained in:
root
2026-04-30 14:59:57 -04:00
parent 2b97c1a04a
commit 695a7f7cc7
92 changed files with 4873 additions and 0 deletions
+824
View File
@@ -0,0 +1,824 @@
generator client {
provider = "prisma-client-js"
output = "../generated"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ═══════════════════════════════════════════════════════════════
// ENUMS
// ═══════════════════════════════════════════════════════════════
enum CompanyStatus { PENDING TRIALING ACTIVE PAST_DUE SUSPENDED CANCELLED }
enum Plan { STARTER GROWTH PRO }
enum BillingPeriod { MONTHLY ANNUAL }
enum SubscriptionStatus { TRIALING ACTIVE PAST_DUE CANCELLED UNPAID }
enum InvoiceStatus { PENDING PAID FAILED REFUNDED }
enum PaymentProvider { AMANPAY PAYPAL }
enum PaymentStatus { PENDING SUCCEEDED FAILED REFUNDED PARTIALLY_REFUNDED }
enum PaymentType { CHARGE DEPOSIT REFUND }
enum EmployeeRole { OWNER MANAGER AGENT }
enum VehicleStatus { AVAILABLE RENTED MAINTENANCE OUT_OF_SERVICE }
enum VehicleCategory { ECONOMY COMPACT MIDSIZE FULLSIZE SUV LUXURY VAN TRUCK }
enum Transmission { AUTOMATIC MANUAL }
enum FuelType { GASOLINE DIESEL ELECTRIC HYBRID }
enum OfferType { PERCENTAGE FIXED_AMOUNT FREE_DAY SPECIAL_RATE }
enum ReservationStatus { DRAFT CONFIRMED ACTIVE COMPLETED CANCELLED NO_SHOW }
enum BookingSource { DASHBOARD PUBLIC_SITE MARKETPLACE API }
enum CancelledBy { COMPANY RENTER SYSTEM }
enum LicenseStatus { PENDING VALID EXPIRING APPROVED DENIED EXPIRED }
enum InsuranceType { CDW SCDW THEFT THIRD_PARTY FULL BASIC ROADSIDE PERSONAL CUSTOM }
enum InsuranceChargeType { PER_DAY PER_RENTAL PERCENTAGE_OF_RENTAL }
enum DamageReportType { CHECKIN CHECKOUT }
enum PricingRuleType { SURCHARGE DISCOUNT }
enum PricingCondition { AGE_LESS_THAN AGE_GREATER_THAN LICENSE_YEARS_LESS_THAN LICENSE_YEARS_GREATER_THAN }
enum PriceAdjustmentType { PERCENTAGE FLAT_PER_DAY FLAT_TOTAL }
enum AdditionalDriverCharge { FREE PER_DAY FLAT }
enum FuelPolicyType { FULL_TO_FULL FULL_TO_EMPTY SAME_TO_SAME PREPAID FREE }
enum InspectionType { CHECKIN CHECKOUT }
enum FuelLevel { FULL SEVEN_EIGHTHS THREE_QUARTERS FIVE_EIGHTHS HALF THREE_EIGHTHS QUARTER ONE_EIGHTH EMPTY }
enum DiagramView { TOP FRONT REAR LEFT RIGHT }
enum DamageType { SCRATCH DENT CRACK CHIP MISSING STAIN OTHER }
enum DamageSeverity { MINOR MODERATE MAJOR }
enum ReportingPeriod { WEEKLY MONTHLY QUARTERLY ANNUAL }
enum ReportFormat { PDF CSV BOTH }
enum AdminRole { SUPER_ADMIN ADMIN SUPPORT FINANCE VIEWER }
enum AdminResource { COMPANIES COMPANY_EMPLOYEES SUBSCRIPTIONS PAYMENTS OFFERS RENTERS ADMIN_USERS AUDIT_LOGS PLATFORM_METRICS }
enum AdminAction { READ CREATE UPDATE DELETE SUSPEND IMPERSONATE }
enum NotificationType {
NEW_BOOKING BOOKING_CANCELLED PAYMENT_RECEIVED PAYMENT_FAILED
SUBSCRIPTION_TRIAL_ENDING SUBSCRIPTION_SUSPENDED
VEHICLE_MAINTENANCE_DUE OFFER_EXPIRING NEW_REVIEW_RECEIVED
BOOKING_CONFIRMED PICKUP_REMINDER_24H PICKUP_REMINDER_2H
VEHICLE_READY RETURN_REMINDER BOOKING_CANCELLED_BY_COMPANY
REFUND_PROCESSED NEW_OFFER_FROM_SAVED_COMPANY REVIEW_REQUEST
}
enum NotificationChannel { EMAIL SMS WHATSAPP IN_APP PUSH }
enum NotificationStatus { PENDING SENT DELIVERED FAILED READ }
// ═══════════════════════════════════════════════════════════════
// B2B — COMPANY SIDE
// ═══════════════════════════════════════════════════════════════
model Company {
id String @id @default(cuid())
name String
slug String @unique
email String @unique
phone String?
address Json?
status CompanyStatus @default(PENDING)
subscriptionPaymentRef String?
apiKey String @unique @default(cuid())
subscription Subscription?
brand BrandSettings?
employees Employee[]
vehicles Vehicle[]
offers Offer[]
reservations Reservation[]
customers Customer[]
rentalPayments RentalPayment[]
subscriptionInvoices SubscriptionInvoice[]
contractSettings ContractSettings?
accountingSettings AccountingSettings?
insurancePolicies InsurancePolicy[]
pricingRules PricingRule[]
notifications Notification[] @relation("CompanyNotifications")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("companies")
}
model Subscription {
id String @id @default(cuid())
companyId String @unique
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
plan Plan @default(STARTER)
billingPeriod BillingPeriod @default(MONTHLY)
status SubscriptionStatus @default(TRIALING)
currency String @default("MAD")
trialStartAt DateTime?
trialEndAt DateTime?
currentPeriodStart DateTime?
currentPeriodEnd DateTime?
cancelledAt DateTime?
cancelAtPeriodEnd Boolean @default(false)
invoices SubscriptionInvoice[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("subscriptions")
}
model SubscriptionInvoice {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
subscriptionId String
subscription Subscription @relation(fields: [subscriptionId], references: [id])
amount Int
currency String @default("MAD")
status InvoiceStatus
amanpayTransactionId String? @unique
paypalCaptureId String? @unique
paymentProvider PaymentProvider @default(AMANPAY)
paidAt DateTime?
createdAt DateTime @default(now())
@@index([companyId])
@@map("subscription_invoices")
}
model BrandSettings {
id String @id @default(cuid())
companyId String @unique
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
displayName String
tagline String?
logoUrl String?
faviconUrl String?
heroImageUrl String?
primaryColor String @default("#1A56DB")
accentColor String @default("#F59E0B")
subdomain String @unique
customDomain String? @unique
customDomainVerified Boolean @default(false)
customDomainAddedAt DateTime?
publicEmail String?
publicPhone String?
publicAddress String?
publicCity String?
publicCountry String?
websiteUrl String?
whatsappNumber String?
facebookUrl String?
instagramUrl String?
defaultLocale String @default("en")
defaultCurrency String @default("MAD")
amanpayMerchantId String?
amanpaySecretKey String?
paypalEmail String?
paypalMerchantId String?
paymentMethodsEnabled PaymentProvider[]
isListedOnMarketplace Boolean @default(true)
marketplaceRating Float?
updatedAt DateTime @updatedAt
@@map("brand_settings")
}
model Employee {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
clerkUserId String @unique
firstName String
lastName String
email String
phone String?
role EmployeeRole @default(AGENT)
isActive Boolean @default(true)
notifications Notification[] @relation("EmployeeNotifications")
notificationPreferences NotificationPreference[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@map("employees")
}
model Vehicle {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
make String
model String
year Int
color String
licensePlate String
vin String?
category VehicleCategory @default(ECONOMY)
seats Int @default(5)
transmission Transmission @default(AUTOMATIC)
fuelType FuelType @default(GASOLINE)
features String[]
dailyRate Int
status VehicleStatus @default(AVAILABLE)
photos String[]
mileage Int?
notes String?
isPublished Boolean @default(true)
reservations Reservation[]
maintenance MaintenanceLog[]
offerVehicles OfferVehicle[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@index([companyId, status])
@@index([companyId, isPublished])
@@map("vehicles")
}
model Offer {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
title String
description String?
termsAndConds String?
imageUrl String?
type OfferType
discountValue Int
specialRate Int?
appliesToAll Boolean @default(true)
categories VehicleCategory[]
minRentalDays Int?
maxRentalDays Int?
promoCode String? @unique
maxRedemptions Int?
redemptionCount Int @default(0)
validFrom DateTime
validUntil DateTime
isActive Boolean @default(true)
isPublic Boolean @default(true)
isFeatured Boolean @default(false)
vehicles OfferVehicle[]
reservations Reservation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@index([isPublic, isActive, validUntil])
@@map("offers")
}
model OfferVehicle {
offerId String
offer Offer @relation(fields: [offerId], references: [id], onDelete: Cascade)
vehicleId String
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
@@id([offerId, vehicleId])
@@map("offer_vehicles")
}
// ═══════════════════════════════════════════════════════════════
// B2C — RENTER SIDE
// ═══════════════════════════════════════════════════════════════
model Renter {
id String @id @default(cuid())
firstName String
lastName String
email String @unique
phone String?
passwordHash String
driverLicense String?
dateOfBirth DateTime?
nationality String?
preferredLocale String @default("en")
preferredCurrency String @default("MAD")
fcmToken String?
emailVerified Boolean @default(false)
phoneVerified Boolean @default(false)
isActive Boolean @default(true)
reservations Reservation[] @relation("RenterReservations")
savedCompanies RenterSavedCompany[]
reviews Review[]
notifications Notification[] @relation("RenterNotifications")
notificationPreferences NotificationPreference[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("renters")
}
model RenterSavedCompany {
renterId String
renter Renter @relation(fields: [renterId], references: [id], onDelete: Cascade)
companyId String
savedAt DateTime @default(now())
@@id([renterId, companyId])
@@map("renter_saved_companies")
}
model Customer {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
renterId String?
firstName String
lastName String
email String
phone String?
driverLicense String?
dateOfBirth DateTime?
nationality String?
address Json?
notes String?
flagged Boolean @default(false)
flagReason String?
licenseExpiry DateTime?
licenseIssuedAt DateTime?
licenseCountry String?
licenseNumber String?
licenseCategory String?
licenseExpired Boolean @default(false)
licenseExpiringSoon Boolean @default(false)
licenseValidationStatus LicenseStatus @default(PENDING)
licenseApprovedBy String?
licenseApprovedAt DateTime?
licenseApprovalNote String?
reservations Reservation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([companyId, email])
@@index([companyId])
@@map("customers")
}
model Reservation {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
vehicleId String
vehicle Vehicle @relation(fields: [vehicleId], references: [id])
customerId String
customer Customer @relation(fields: [customerId], references: [id])
renterId String?
renter Renter? @relation("RenterReservations", fields: [renterId], references: [id])
offerId String?
offer Offer? @relation(fields: [offerId], references: [id])
promoCodeUsed String?
source BookingSource @default(DASHBOARD)
marketplaceRef String?
status ReservationStatus @default(DRAFT)
startDate DateTime
endDate DateTime
pickupLocation String?
returnLocation String?
dailyRate Int
discountAmount Int @default(0)
totalDays Int
totalAmount Int
depositAmount Int @default(0)
checkedInAt DateTime?
checkedOutAt DateTime?
checkInMileage Int?
checkOutMileage Int?
notes String?
cancelReason String?
cancelledBy CancelledBy?
contractNumber String? @unique
invoiceNumber String? @unique
checkInFuelLevel String?
checkOutFuelLevel String?
insurances ReservationInsurance[]
insuranceTotal Int @default(0)
additionalDrivers AdditionalDriver[]
additionalDriverTotal Int @default(0)
pricingRulesApplied Json?
pricingRulesTotal Int @default(0)
damageReports DamageReport[]
rentalPayments RentalPayment[]
inspections DamageInspection[]
review Review?
damageChargeAmount Int?
damageChargeNote String?
extras Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@index([companyId, status])
@@index([vehicleId, startDate, endDate])
@@index([renterId])
@@map("reservations")
}
model RentalPayment {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
reservationId String
reservation Reservation @relation(fields: [reservationId], references: [id])
amount Int
currency String @default("MAD")
status PaymentStatus @default(PENDING)
type PaymentType @default(CHARGE)
paymentProvider PaymentProvider
amanpayTransactionId String? @unique
paypalCaptureId String? @unique
paymentMethod String?
paidAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@index([reservationId])
@@map("rental_payments")
}
model Review {
id String @id @default(cuid())
reservationId String @unique
reservation Reservation @relation(fields: [reservationId], references: [id])
renterId String
renter Renter @relation(fields: [renterId], references: [id])
companyId String
overallRating Int
vehicleRating Int?
serviceRating Int?
comment String?
isPublished Boolean @default(true)
companyReply String?
companyRepliedAt DateTime?
createdAt DateTime @default(now())
@@index([companyId])
@@map("reviews")
}
// ═══════════════════════════════════════════════════════════════
// NOTIFICATIONS
// ═══════════════════════════════════════════════════════════════
model Notification {
id String @id @default(cuid())
companyId String?
company Company? @relation("CompanyNotifications", fields: [companyId], references: [id], onDelete: Cascade)
employeeId String?
employee Employee? @relation("EmployeeNotifications", fields: [employeeId], references: [id], onDelete: Cascade)
renterId String?
renter Renter? @relation("RenterNotifications", fields: [renterId], references: [id], onDelete: Cascade)
type NotificationType
title String
body String
data Json?
channel NotificationChannel
status NotificationStatus @default(PENDING)
sentAt DateTime?
failReason String?
readAt DateTime?
providerMessageId String?
createdAt DateTime @default(now())
@@index([companyId])
@@index([renterId])
@@map("notifications")
}
model NotificationPreference {
id String @id @default(cuid())
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: Cascade)
renterId String?
renter Renter? @relation(fields: [renterId], references: [id], onDelete: Cascade)
notificationType NotificationType
channel NotificationChannel
enabled Boolean @default(true)
@@unique([employeeId, notificationType, channel])
@@unique([renterId, notificationType, channel])
@@map("notification_preferences")
}
// ═══════════════════════════════════════════════════════════════
// MAINTENANCE
// ═══════════════════════════════════════════════════════════════
model MaintenanceLog {
id String @id @default(cuid())
vehicleId String
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
type String
description String?
cost Int?
mileage Int?
performedAt DateTime
nextDueAt DateTime?
nextDueMileage Int?
createdAt DateTime @default(now())
@@index([vehicleId])
@@map("maintenance_logs")
}
// ═══════════════════════════════════════════════════════════════
// DOCUMENTS — CONTRACT SETTINGS
// ═══════════════════════════════════════════════════════════════
model ContractSettings {
id String @id @default(cuid())
companyId String @unique
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
legalName String?
registrationNumber String?
taxId String?
terms String @default("")
fuelPolicy String @default("The vehicle must be returned with the same fuel level as at pickup.")
depositPolicy String @default("The security deposit is refundable within 7 days after return, subject to vehicle inspection.")
lateFeePolicy String @default("Late returns will incur a charge of 1 additional day rate per hour of delay.")
damagePolicy String @default("The renter is liable for any damage not covered by insurance.")
additionalClauses String[] @default([])
signatureRequired Boolean @default(true)
contractFooterNote String?
invoiceFooterNote String?
showTax Boolean @default(false)
taxRate Float?
taxLabel String?
contractPrefix String @default("CNT")
invoicePrefix String @default("INV")
fuelPolicyType FuelPolicyType @default(FULL_TO_FULL)
fuelPolicyNote String?
fuelChargePerLiter Int?
fuelShortfallFee Int?
lateFeePerHour Int?
taxLines Json?
lastContractSeq Int @default(0)
lastInvoiceSeq Int @default(0)
additionalDriverCharge AdditionalDriverCharge @default(FREE)
additionalDriverDailyRate Int @default(0)
additionalDriverFlatRate Int @default(0)
updatedAt DateTime @updatedAt
@@map("contract_settings")
}
// ═══════════════════════════════════════════════════════════════
// INSURANCE
// ═══════════════════════════════════════════════════════════════
model InsurancePolicy {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
name String
description String?
type InsuranceType
chargeType InsuranceChargeType
chargeValue Int
isRequired Boolean @default(false)
isActive Boolean @default(true)
sortOrder Int @default(0)
reservations ReservationInsurance[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@index([companyId, isActive])
@@map("insurance_policies")
}
model ReservationInsurance {
id String @id @default(cuid())
reservationId String
reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade)
insurancePolicyId String
insurancePolicy InsurancePolicy @relation(fields: [insurancePolicyId], references: [id])
policyName String
policyType InsuranceType
chargeType InsuranceChargeType
chargeValue Int
totalCharge Int
@@unique([reservationId, insurancePolicyId])
@@map("reservation_insurances")
}
// ═══════════════════════════════════════════════════════════════
// ADDITIONAL DRIVERS
// ═══════════════════════════════════════════════════════════════
model AdditionalDriver {
id String @id @default(cuid())
reservationId String
reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade)
companyId String
firstName String
lastName String
email String?
phone String?
driverLicense String
licenseExpiry DateTime?
licenseIssuedAt DateTime?
dateOfBirth DateTime?
nationality String?
chargeType AdditionalDriverCharge @default(FREE)
chargeValue Int @default(0)
totalCharge Int @default(0)
licenseExpired Boolean @default(false)
licenseExpiringSoon Boolean @default(false)
requiresApproval Boolean @default(false)
approvedBy String?
approvedAt DateTime?
approvalNote String?
createdAt DateTime @default(now())
@@index([reservationId])
@@index([companyId])
@@map("additional_drivers")
}
// ═══════════════════════════════════════════════════════════════
// DAMAGE REPORTS
// ═══════════════════════════════════════════════════════════════
model DamageReport {
id String @id @default(cuid())
reservationId String
reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade)
companyId String
type DamageReportType
damages Json
photos String[]
fuelLevel FuelLevel @default(FULL)
mileage Int?
inspectedAt DateTime @default(now())
inspectedBy String?
customerSignedAt DateTime?
customerName String?
@@unique([reservationId, type])
@@index([companyId])
@@map("damage_reports")
}
model DamageInspection {
id String @id @default(cuid())
reservationId String
reservation Reservation @relation(fields: [reservationId], references: [id], onDelete: Cascade)
companyId String
type InspectionType
mileage Int?
fuelLevel FuelLevel
fuelCharge Int?
generalCondition String?
employeeNotes String?
customerAgreed Boolean @default(false)
inspectedAt DateTime @default(now())
inspectedBy String?
damagePoints DamagePoint[]
createdAt DateTime @default(now())
@@unique([reservationId, type])
@@index([reservationId])
@@index([companyId])
@@map("damage_inspections")
}
model DamagePoint {
id String @id @default(cuid())
inspectionId String
inspection DamageInspection @relation(fields: [inspectionId], references: [id], onDelete: Cascade)
viewType DiagramView
x Float
y Float
damageType DamageType
severity DamageSeverity @default(MINOR)
description String?
isPreExisting Boolean @default(false)
@@index([inspectionId])
@@map("damage_points")
}
// ═══════════════════════════════════════════════════════════════
// PRICING RULES
// ═══════════════════════════════════════════════════════════════
model PricingRule {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
name String
type PricingRuleType
condition PricingCondition
conditionValue Int
adjustmentType PriceAdjustmentType
adjustmentValue Int
isActive Boolean @default(true)
description String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([companyId])
@@map("pricing_rules")
}
// ═══════════════════════════════════════════════════════════════
// ACCOUNTING
// ═══════════════════════════════════════════════════════════════
model AccountingSettings {
id String @id @default(cuid())
companyId String @unique
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
reportingPeriod ReportingPeriod @default(MONTHLY)
fiscalYearStart Int @default(1)
currency String @default("MAD")
accountantEmail String?
accountantName String?
autoSendReport Boolean @default(false)
reportFormat ReportFormat @default(PDF)
updatedAt DateTime @updatedAt
@@map("accounting_settings")
}
// ═══════════════════════════════════════════════════════════════
// ADMIN PANEL
// ═══════════════════════════════════════════════════════════════
model AdminUser {
id String @id @default(cuid())
email String @unique
firstName String
lastName String
passwordHash String
role AdminRole @default(SUPPORT)
isActive Boolean @default(true)
totpSecret String?
totpEnabled Boolean @default(false)
lastLoginAt DateTime?
lastLoginIp String?
auditLogs AuditLog[]
permissions AdminPermission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("admin_users")
}
model AdminPermission {
id String @id @default(cuid())
adminUserId String
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
resource AdminResource
actions AdminAction[]
@@unique([adminUserId, resource])
@@map("admin_permissions")
}
model AuditLog {
id String @id @default(cuid())
adminUserId String
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
action String
resource String
resourceId String?
companyId String?
before Json?
after Json?
ipAddress String?
userAgent String?
note String?
createdAt DateTime @default(now())
@@index([adminUserId])
@@index([companyId])
@@index([action])
@@map("audit_logs")
}
+40
View File
@@ -0,0 +1,40 @@
import { PrismaClient } from '../generated'
import bcrypt from 'bcryptjs'
const prisma = new PrismaClient()
async function main() {
const email = process.env.ADMIN_SEED_EMAIL ?? 'admin@rentaldrivego.com'
const password = process.env.ADMIN_SEED_PASSWORD ?? 'changeme123'
const firstName = process.env.ADMIN_SEED_FIRST_NAME ?? 'Super'
const lastName = process.env.ADMIN_SEED_LAST_NAME ?? 'Admin'
const existing = await prisma.adminUser.findUnique({ where: { email } })
if (existing) {
console.log(`Super admin already exists: ${email}`)
return
}
const passwordHash = await bcrypt.hash(password, 12)
const admin = await prisma.adminUser.create({
data: {
email,
firstName,
lastName,
passwordHash,
role: 'SUPER_ADMIN',
isActive: true,
},
})
console.log(`Created SUPER_ADMIN: ${admin.email} (id: ${admin.id})`)
console.log('Login with the password you set in ADMIN_SEED_PASSWORD')
}
main()
.catch((err) => {
console.error(err)
process.exit(1)
})
.finally(() => prisma.$disconnect())