Dental Care
This commit is contained in:
106
prisma/cleanup-orphaned-appointments.ts
Normal file
106
prisma/cleanup-orphaned-appointments.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Cleanup script to remove orphaned appointments
|
||||
*
|
||||
* Orphaned appointments are appointments that reference non-existent:
|
||||
* - patients (patientId doesn't exist in User table)
|
||||
* - dentists (dentistId doesn't exist in User table)
|
||||
* - services (serviceId doesn't exist in Service table)
|
||||
*
|
||||
* Run with: npx ts-node --project prisma/tsconfig.json prisma/cleanup-orphaned-appointments.ts
|
||||
*/
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function cleanupOrphanedAppointments() {
|
||||
console.log("🔍 Starting cleanup of orphaned appointments...\n");
|
||||
|
||||
try {
|
||||
// Get all appointments
|
||||
const allAppointments = await prisma.appointment.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
patientId: true,
|
||||
dentistId: true,
|
||||
serviceId: true,
|
||||
date: true,
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`📊 Found ${allAppointments.length} total appointments\n`);
|
||||
|
||||
// Get all valid IDs
|
||||
const [validPatientIds, validDentistIds, validServiceIds] = await Promise.all([
|
||||
prisma.user.findMany({
|
||||
select: { id: true },
|
||||
where: { role: "patient" },
|
||||
}),
|
||||
prisma.user.findMany({
|
||||
select: { id: true },
|
||||
where: { role: "dentist" },
|
||||
}),
|
||||
prisma.service.findMany({
|
||||
select: { id: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
const validPatientIdSet = new Set(validPatientIds.map((u) => u.id));
|
||||
const validDentistIdSet = new Set(validDentistIds.map((u) => u.id));
|
||||
const validServiceIdSet = new Set(validServiceIds.map((s) => s.id));
|
||||
|
||||
// Find orphaned appointments
|
||||
const orphanedAppointments = allAppointments.filter(
|
||||
(apt) =>
|
||||
!validPatientIdSet.has(apt.patientId) ||
|
||||
!validDentistIdSet.has(apt.dentistId) ||
|
||||
!validServiceIdSet.has(apt.serviceId)
|
||||
);
|
||||
|
||||
if (orphanedAppointments.length === 0) {
|
||||
console.log("✅ No orphaned appointments found. Database is clean!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`⚠️ Found ${orphanedAppointments.length} orphaned appointments:\n`);
|
||||
|
||||
// Log details
|
||||
orphanedAppointments.forEach((apt) => {
|
||||
const issues: string[] = [];
|
||||
if (!validPatientIdSet.has(apt.patientId)) {
|
||||
issues.push(`invalid patientId: ${apt.patientId}`);
|
||||
}
|
||||
if (!validDentistIdSet.has(apt.dentistId)) {
|
||||
issues.push(`invalid dentistId: ${apt.dentistId}`);
|
||||
}
|
||||
if (!validServiceIdSet.has(apt.serviceId)) {
|
||||
issues.push(`invalid serviceId: ${apt.serviceId}`);
|
||||
}
|
||||
console.log(` - Appointment ${apt.id}: ${issues.join(", ")} (Status: ${apt.status}, Date: ${apt.date.toISOString()})`);
|
||||
});
|
||||
|
||||
console.log("\n🗑️ Deleting orphaned appointments...\n");
|
||||
|
||||
// Delete orphaned appointments
|
||||
const orphanedIds = orphanedAppointments.map((apt) => apt.id);
|
||||
|
||||
const result = await prisma.appointment.deleteMany({
|
||||
where: {
|
||||
id: { in: orphanedIds },
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ Successfully deleted ${result.count} orphaned appointment(s)\n`);
|
||||
console.log("🎉 Cleanup complete!\n");
|
||||
} catch (error) {
|
||||
console.error("❌ Error during cleanup:", error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// Run cleanup
|
||||
cleanupOrphanedAppointments();
|
||||
|
||||
95
prisma/create-indexes.ts
Normal file
95
prisma/create-indexes.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
/**
|
||||
* Creates database indexes for optimal query performance
|
||||
* Run with: npx ts-node prisma/create-indexes.ts
|
||||
*/
|
||||
async function createIndexes() {
|
||||
if (!process.env.DATABASE_URL) {
|
||||
console.error("❌ DATABASE_URL environment variable is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = new MongoClient(process.env.DATABASE_URL);
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
console.log("✅ Connected to MongoDB");
|
||||
|
||||
const db = client.db();
|
||||
|
||||
console.log("📊 Creating indexes...");
|
||||
|
||||
// Appointment indexes for status, date, and user filtering
|
||||
console.log(" - Creating appointment indexes...");
|
||||
await db.collection("appointment").createIndex({ status: 1 });
|
||||
await db.collection("appointment").createIndex({ date: 1 });
|
||||
await db.collection("appointment").createIndex({ createdAt: -1 });
|
||||
await db
|
||||
.collection("appointment")
|
||||
.createIndex({ patientId: 1, status: 1, date: 1 });
|
||||
await db
|
||||
.collection("appointment")
|
||||
.createIndex({ dentistId: 1, status: 1, date: 1 });
|
||||
await db
|
||||
.collection("appointment")
|
||||
.createIndex({ status: 1, updatedAt: -1 });
|
||||
|
||||
// User indexes for role-based queries
|
||||
console.log(" - Creating user indexes...");
|
||||
await db.collection("user").createIndex({ role: 1 });
|
||||
await db.collection("user").createIndex({ role: 1, createdAt: -1 });
|
||||
await db.collection("user").createIndex({ emailVerified: 1 });
|
||||
await db.collection("user").createIndex({ role: 1, isAvailable: 1 });
|
||||
|
||||
// Payment indexes for status and user queries
|
||||
console.log(" - Creating payment indexes...");
|
||||
await db.collection("payment").createIndex({ status: 1 });
|
||||
await db.collection("payment").createIndex({ userId: 1, status: 1 });
|
||||
await db.collection("payment").createIndex({ paidAt: -1 });
|
||||
await db.collection("payment").createIndex({ status: 1, paidAt: -1 });
|
||||
|
||||
// Service indexes
|
||||
console.log(" - Creating service indexes...");
|
||||
await db.collection("service").createIndex({ isActive: 1 });
|
||||
await db.collection("service").createIndex({ category: 1, isActive: 1 });
|
||||
|
||||
// Session indexes for auth performance
|
||||
console.log(" - Creating session indexes...");
|
||||
await db.collection("session").createIndex({ expiresAt: 1 });
|
||||
await db.collection("session").createIndex({ userId: 1 });
|
||||
|
||||
console.log("\n✅ All indexes created successfully!");
|
||||
|
||||
// Display created indexes
|
||||
console.log("\n📋 Index Summary:");
|
||||
const collections = [
|
||||
"appointment",
|
||||
"user",
|
||||
"payment",
|
||||
"service",
|
||||
"session",
|
||||
];
|
||||
for (const collectionName of collections) {
|
||||
const indexes = await db.collection(collectionName).indexes();
|
||||
console.log(`\n${collectionName}:`);
|
||||
indexes.forEach((index) => {
|
||||
const keyStr = Object.entries(index.key)
|
||||
.map(([k, v]) => `${k}:${v}`)
|
||||
.join(", ");
|
||||
console.log(` - ${index.name}: { ${keyStr} }`);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error creating indexes:", error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await client.close();
|
||||
console.log("\n✅ Disconnected from MongoDB");
|
||||
}
|
||||
}
|
||||
|
||||
createIndexes().catch((error) => {
|
||||
console.error("❌ Fatal error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
152
prisma/dentist-services-seed/seed.ts
Normal file
152
prisma/dentist-services-seed/seed.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { doctors } from"lib/types/doctor";
|
||||
import { allServices } from "lib/types/services-data";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Enhanced dentist data with full profiles
|
||||
const dentistProfiles = [
|
||||
{
|
||||
...doctors[0],
|
||||
email: "kath.estrada@dentalucare.com",
|
||||
phone: "+63 912 345 6789",
|
||||
qualifications: "Doctor of Dental Medicine (DMD), Orthodontics Specialist",
|
||||
experience: 8,
|
||||
},
|
||||
{
|
||||
...doctors[1],
|
||||
email: "clyrelle.cervantes@dentalucare.com",
|
||||
phone: "+63 912 345 6790",
|
||||
qualifications:
|
||||
"Doctor of Dental Surgery (DDS), Cosmetic Dentistry Specialist",
|
||||
experience: 6,
|
||||
},
|
||||
{
|
||||
...doctors[2],
|
||||
email: "von.arguelles@dentalucare.com",
|
||||
phone: "+63 912 345 6791",
|
||||
qualifications: "Doctor of Dental Medicine (DMD), Oral Surgery Specialist",
|
||||
experience: 10,
|
||||
},
|
||||
{
|
||||
...doctors[3],
|
||||
email: "dexter.cabanag@dentalucare.com",
|
||||
phone: "+63 912 345 6792",
|
||||
qualifications: "Doctor of Dental Surgery (DDS), Periodontics Specialist",
|
||||
experience: 7,
|
||||
},
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Starting database seeding...");
|
||||
|
||||
// Clear existing data (optional - comment out if you want to keep existing data)
|
||||
console.log("🗑️ Cleaning existing data...");
|
||||
await prisma.service.deleteMany({});
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
role: "dentist",
|
||||
},
|
||||
});
|
||||
|
||||
// Seed Dentists
|
||||
console.log("👨⚕️ Seeding dentists...");
|
||||
const createdDentists = [];
|
||||
const password = "dentalucaredentist@123"; // Plain text password (better-auth will hash it)
|
||||
|
||||
for (const doctor of dentistProfiles) {
|
||||
const dentist = await prisma.user.create({
|
||||
data: {
|
||||
id: doctor.id,
|
||||
name: doctor.name,
|
||||
email: doctor.email,
|
||||
emailVerified: true,
|
||||
role: "dentist",
|
||||
image: doctor.avatar,
|
||||
specialization: doctor.role,
|
||||
isAvailable: true,
|
||||
phone: doctor.phone,
|
||||
qualifications: doctor.qualifications,
|
||||
experience: doctor.experience,
|
||||
workingHours: {
|
||||
monday: { start: "09:00", end: "17:00" },
|
||||
tuesday: { start: "09:00", end: "17:00" },
|
||||
wednesday: { start: "09:00", end: "17:00" },
|
||||
thursday: { start: "09:00", end: "17:00" },
|
||||
friday: { start: "09:00", end: "17:00" },
|
||||
saturday: { start: "09:00", end: "13:00" },
|
||||
sunday: { closed: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create account with password for the dentist (better-auth uses "credential" as providerId)
|
||||
await prisma.account.create({
|
||||
data: {
|
||||
id: `${dentist.id}:credential`,
|
||||
userId: dentist.id,
|
||||
accountId: `${dentist.id}:credential`,
|
||||
providerId: "credential",
|
||||
password: password,
|
||||
},
|
||||
});
|
||||
|
||||
createdDentists.push(dentist);
|
||||
console.log(
|
||||
` ✓ Created dentist: ${dentist.name} (${dentist.specialization}) - email: ${doctor.email}`
|
||||
);
|
||||
console.log(` Password: dentalucaredentist@123`);
|
||||
}
|
||||
|
||||
// Seed Services
|
||||
console.log("🦷 Seeding services...");
|
||||
const createdServices = [];
|
||||
|
||||
for (const service of allServices) {
|
||||
const createdService = await prisma.service.create({
|
||||
data: {
|
||||
id: service.id, // Use the ID from the service data
|
||||
name: service.name,
|
||||
description: service.description || service.name,
|
||||
duration: service.duration,
|
||||
price: service.price, // Keep price as string (e.g., "₱1,500 – ₱3,000")
|
||||
category: service.category,
|
||||
isActive: service.isActive,
|
||||
},
|
||||
});
|
||||
createdServices.push(createdService);
|
||||
console.log(
|
||||
` ✓ Created service: ${createdService.name} (${createdService.price})`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("\n✅ Seeding completed successfully!");
|
||||
console.log(`📊 Summary:`);
|
||||
console.log(` - Dentists created: ${createdDentists.length}`);
|
||||
console.log(` - Services created: ${createdServices.length}`);
|
||||
console.log("\n👨⚕️ Dentists:");
|
||||
createdDentists.forEach((dentist) => {
|
||||
console.log(` - ${dentist.name} (${dentist.specialization})`);
|
||||
});
|
||||
console.log("\n🦷 Service Categories:");
|
||||
const categoryCounts = createdServices.reduce(
|
||||
(acc: Record<string, number>, service) => {
|
||||
acc[service.category] = (acc[service.category] || 0) + 1;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
Object.entries(categoryCounts).forEach(([category, count]) => {
|
||||
console.log(` - ${category}: ${count} services`);
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error("❌ Error during seeding:", e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
228
prisma/schema.prisma
Normal file
228
prisma/schema.prisma
Normal file
@@ -0,0 +1,228 @@
|
||||
|
||||
datasource db {
|
||||
provider = "mongodb"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
enum role {
|
||||
patient
|
||||
dentist
|
||||
admin
|
||||
}
|
||||
|
||||
enum AppointmentStatus {
|
||||
pending
|
||||
confirmed
|
||||
cancelled
|
||||
completed
|
||||
rescheduled
|
||||
}
|
||||
|
||||
enum PaymentStatus {
|
||||
pending
|
||||
paid
|
||||
failed
|
||||
refunded
|
||||
}
|
||||
|
||||
enum PaymentMethod {
|
||||
card
|
||||
e_wallet
|
||||
bank_transfer
|
||||
cash
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @map("_id")
|
||||
name String
|
||||
email String
|
||||
emailVerified Boolean @default(false)
|
||||
image String?
|
||||
role role @default(patient)
|
||||
phone String?
|
||||
address String?
|
||||
dateOfBirth DateTime?
|
||||
medicalHistory String?
|
||||
stripeCustomerId String? // Stripe customer ID
|
||||
// Dentist-specific fields
|
||||
specialization String?
|
||||
qualifications String?
|
||||
experience Int? // Years of experience
|
||||
workingHours Json? // {monday: {start: "9:00", end: "17:00"}, ...}
|
||||
isAvailable Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
// Relations
|
||||
appointmentsAsPatient Appointment[] @relation("PatientAppointments")
|
||||
appointmentsAsDentist Appointment[] @relation("DentistAppointments")
|
||||
payments Payment[]
|
||||
notifications Notification[]
|
||||
chatMessages ChatMessage[]
|
||||
|
||||
@@unique([email])
|
||||
@@map("user")
|
||||
}
|
||||
|
||||
model Service {
|
||||
id String @id @map("_id") // Use custom ID from seed data
|
||||
name String
|
||||
description String?
|
||||
duration Int // in minutes
|
||||
price String // Price range or single price (e.g., "₱500 – ₱1,500" or "₱1,500")
|
||||
category String
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
appointments Appointment[]
|
||||
|
||||
@@map("service")
|
||||
}
|
||||
|
||||
model Appointment {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
patientId String
|
||||
dentistId String
|
||||
serviceId String // Changed to String to match Service.id
|
||||
date DateTime
|
||||
timeSlot String // e.g., "09:00-10:00"
|
||||
status AppointmentStatus @default(pending)
|
||||
notes String?
|
||||
cancelReason String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
// Relations
|
||||
patient User @relation("PatientAppointments", fields: [patientId], references: [id], onDelete: Cascade)
|
||||
dentist User @relation("DentistAppointments", fields: [dentistId], references: [id], onDelete: Cascade)
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
payment Payment?
|
||||
|
||||
@@map("appointment")
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
appointmentId String @unique @db.ObjectId
|
||||
userId String
|
||||
amount Float
|
||||
method PaymentMethod
|
||||
status PaymentStatus @default(pending)
|
||||
transactionId String?
|
||||
paidAt DateTime?
|
||||
stripeCustomerId String?
|
||||
stripeServicesId String?
|
||||
stripePaymentIntentId String?
|
||||
stripePriceId String?
|
||||
stripeSubscriptionId String?
|
||||
stripeInvoiceId String?
|
||||
stripeRefundId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
// Relations
|
||||
appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("payment")
|
||||
}
|
||||
|
||||
model Notification {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
userId String
|
||||
title String
|
||||
message String
|
||||
type String // email, sms, push
|
||||
isRead Boolean @default(false)
|
||||
sentAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
// Relations
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("notification")
|
||||
}
|
||||
|
||||
model ChatMessage {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
userId String
|
||||
message String
|
||||
isFromUser Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
// Relations
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("chat_message")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @map("_id")
|
||||
expiresAt DateTime
|
||||
token String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
activeOrganizationId String?
|
||||
|
||||
impersonatedBy String?
|
||||
|
||||
@@unique([token])
|
||||
@@map("session")
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @map("_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
|
||||
|
||||
@@map("account")
|
||||
}
|
||||
|
||||
model Verification {
|
||||
id String @id @map("_id")
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
@@map("verification")
|
||||
}
|
||||
|
||||
model Subscription {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
plan String
|
||||
referenceId String @unique
|
||||
stripeCustomerId String?
|
||||
stripeSubscriptionId String?
|
||||
status String @default("incomplete")
|
||||
periodStart DateTime?
|
||||
periodEnd DateTime?
|
||||
cancelAtPeriodEnd Boolean @default(false)
|
||||
seats Int?
|
||||
trialStart DateTime?
|
||||
trialEnd DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("subscription")
|
||||
}
|
||||
|
||||
|
||||
152
prisma/seed.ts
Normal file
152
prisma/seed.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { doctors } from "../lib/types/doctor";
|
||||
import { allServices } from "../lib/types/services-data";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Enhanced dentist data with full profiles
|
||||
const dentistProfiles = [
|
||||
{
|
||||
...doctors[0],
|
||||
email: "kath.estrada@dentalucare.com",
|
||||
phone: "+63 912 345 6789",
|
||||
qualifications: "Doctor of Dental Medicine (DMD), Orthodontics Specialist",
|
||||
experience: 8,
|
||||
},
|
||||
{
|
||||
...doctors[1],
|
||||
email: "clyrelle.cervantes@dentalucare.com",
|
||||
phone: "+63 912 345 6790",
|
||||
qualifications:
|
||||
"Doctor of Dental Surgery (DDS), Cosmetic Dentistry Specialist",
|
||||
experience: 6,
|
||||
},
|
||||
{
|
||||
...doctors[2],
|
||||
email: "von.arguelles@dentalucare.com",
|
||||
phone: "+63 912 345 6791",
|
||||
qualifications: "Doctor of Dental Medicine (DMD), Oral Surgery Specialist",
|
||||
experience: 10,
|
||||
},
|
||||
{
|
||||
...doctors[3],
|
||||
email: "dexter.cabanag@dentalucare.com",
|
||||
phone: "+63 912 345 6792",
|
||||
qualifications: "Doctor of Dental Surgery (DDS), Periodontics Specialist",
|
||||
experience: 7,
|
||||
},
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Starting database seeding...");
|
||||
|
||||
// Clear existing data (optional - comment out if you want to keep existing data)
|
||||
console.log("🗑️ Cleaning existing data...");
|
||||
await prisma.service.deleteMany({});
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
role: "dentist",
|
||||
},
|
||||
});
|
||||
|
||||
// Seed Dentists
|
||||
console.log("👨⚕️ Seeding dentists...");
|
||||
const createdDentists = [];
|
||||
const password = "dentalucaredentist@123"; // Plain text password (better-auth will hash it)
|
||||
|
||||
for (const doctor of dentistProfiles) {
|
||||
const dentist = await prisma.user.create({
|
||||
data: {
|
||||
id: doctor.id,
|
||||
name: doctor.name,
|
||||
email: doctor.email,
|
||||
emailVerified: true,
|
||||
role: "dentist",
|
||||
image: doctor.avatar,
|
||||
specialization: doctor.role,
|
||||
isAvailable: true,
|
||||
phone: doctor.phone,
|
||||
qualifications: doctor.qualifications,
|
||||
experience: doctor.experience,
|
||||
workingHours: {
|
||||
monday: { start: "09:00", end: "17:00" },
|
||||
tuesday: { start: "09:00", end: "17:00" },
|
||||
wednesday: { start: "09:00", end: "17:00" },
|
||||
thursday: { start: "09:00", end: "17:00" },
|
||||
friday: { start: "09:00", end: "17:00" },
|
||||
saturday: { start: "09:00", end: "13:00" },
|
||||
sunday: { closed: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create account with password for the dentist (better-auth uses "credential" as providerId)
|
||||
await prisma.account.create({
|
||||
data: {
|
||||
id: `${dentist.id}:credential`,
|
||||
userId: dentist.id,
|
||||
accountId: `${dentist.id}:credential`,
|
||||
providerId: "credential",
|
||||
password: password,
|
||||
},
|
||||
});
|
||||
|
||||
createdDentists.push(dentist);
|
||||
console.log(
|
||||
` ✓ Created dentist: ${dentist.name} (${dentist.specialization}) - email: ${doctor.email}`
|
||||
);
|
||||
console.log(` Password: dentalucaredentist@123`);
|
||||
}
|
||||
|
||||
// Seed Services
|
||||
console.log("🦷 Seeding services...");
|
||||
const createdServices = [];
|
||||
|
||||
for (const service of allServices) {
|
||||
const createdService = await prisma.service.create({
|
||||
data: {
|
||||
id: service.id, // Use the ID from the service data
|
||||
name: service.name,
|
||||
description: service.description || service.name,
|
||||
duration: service.duration,
|
||||
price: service.price, // Keep price as string (e.g., "₱1,500 – ₱3,000")
|
||||
category: service.category,
|
||||
isActive: service.isActive,
|
||||
},
|
||||
});
|
||||
createdServices.push(createdService);
|
||||
console.log(
|
||||
` ✓ Created service: ${createdService.name} (${createdService.price})`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("\n✅ Seeding completed successfully!");
|
||||
console.log(`📊 Summary:`);
|
||||
console.log(` - Dentists created: ${createdDentists.length}`);
|
||||
console.log(` - Services created: ${createdServices.length}`);
|
||||
console.log("\n👨⚕️ Dentists:");
|
||||
createdDentists.forEach((dentist) => {
|
||||
console.log(` - ${dentist.name} (${dentist.specialization})`);
|
||||
});
|
||||
console.log("\n🦷 Service Categories:");
|
||||
const categoryCounts = createdServices.reduce(
|
||||
(acc: Record<string, number>, service) => {
|
||||
acc[service.category] = (acc[service.category] || 0) + 1;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
Object.entries(categoryCounts).forEach(([category, count]) => {
|
||||
console.log(` - ${category}: ${count} services`);
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error("❌ Error during seeding:", e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
8
prisma/tsconfig.json
Normal file
8
prisma/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user