MIF_E31220277/denta-api/prisma/seeds/users.ts

691 lines
22 KiB
TypeScript

import { PrismaClient, Role, StatusKoas } from '@/prisma/generated/client';
import firebaseAdmin from '../../lib/firebase-admin';
import { UserRecord } from 'firebase-admin/auth';
import { faker } from '@faker-js/faker';
import { UserSeedResult, FirebaseCreateData, KoasData } from '../types/seeding';
import { koasUser } from '../data/users';
// Helper function to generate avatar URL based on gender
const getGenderBasedAvatar = (gender?: string): string => {
// Default to random gender if not specified
const sex = gender?.toLowerCase() === 'female' ? 'female' : 'male';
return faker.image.personPortrait({ sex });
};
// Create a helper function to generate a short bio that won't exceed DB limits
const generateShortBio = (): string => {
return faker.lorem.sentence(5);
};
// Helper function to generate a consistent phone number
const generatePhoneNumber = (): string => {
return `08${faker.string.numeric(10)}`;
};
// Helper function to generate WhatsApp link from phone number
const generateWhatsAppLink = (phone: string): string => {
const cleanPhone = phone.replace(/[+\s]/g, '');
return `https://wa.me/${cleanPhone}`;
};
// Helper function to generate a unique name
const generateUniqueName = (
firstName: string,
lastName: string,
index: number
): string => {
return `${firstName} ${lastName}${index > 0 ? ` ${index}` : ''}`;
};
export const seedUsers = async (
prisma: PrismaClient,
resetFirebase: boolean = false
): Promise<UserSeedResult> => {
try {
console.log('Starting user seeding process...');
console.log(
`Firebase reset option: ${resetFirebase ? 'ENABLED' : 'DISABLED'}`
);
// Always delete database records first
console.log('Clearing database records...');
// Need to clear related tables first due to foreign key constraints
await prisma.user.deleteMany({});
await prisma.notification.deleteMany({});
await prisma.like.deleteMany({});
await prisma.review.deleteMany({});
await prisma.appointment.deleteMany({});
await prisma.timeslot.deleteMany({});
await prisma.schedule.deleteMany({});
await prisma.post.deleteMany({});
await prisma.koasProfile.deleteMany({});
await prisma.pasienProfile.deleteMany({});
await prisma.fasilitatorProfile.deleteMany({});
await prisma.session.deleteMany({});
await prisma.account.deleteMany({});
console.log('All database records cleared');
// Get or create university
const universities = await prisma.university.findMany();
let defaultUniversity = universities.length > 0 ? universities[0] : null;
if (!defaultUniversity) {
defaultUniversity = await prisma.university.create({
data: {
name: 'Universitas Negeri Jember',
alias: 'UNEJ',
location: 'Jl. Kalimantan No.37, Krajan Timur, Sumbersari, Jember',
latitude: -8.1649,
longitude: 113.7169,
image:
'https://upload.wikimedia.org/wikipedia/id/7/70/Logo_Universitas_Jember.png',
},
});
console.log('Created default university:', defaultUniversity.name);
}
const universityId = defaultUniversity.id;
const phoneNumbers = new Map<string, string>();
// Store existing Firebase users if we're not resetting
let existingFirebaseUsers: UserRecord[] = [];
if (resetFirebase) {
// If we're resetting Firebase, we'll delete and recreate all users
console.log('Resetting Firebase users...');
try {
// Get all current Firebase users
const listUsersResult = await firebaseAdmin.auth().listUsers(1000);
// Delete all existing Firebase users
const deletePromises = listUsersResult.users.map((user) =>
firebaseAdmin.auth().deleteUser(user.uid)
);
await Promise.all(deletePromises);
console.log(`Deleted ${listUsersResult.users.length} Firebase users`);
} catch (error) {
console.error('Error deleting Firebase users:', error);
// Continue with creating new users
}
// Create new users from scratch
return await createAllUsersFromScratch(prisma, universityId);
} else {
// If not resetting Firebase, we'll use existing Firebase users and sync with database
console.log('Preserving Firebase users and syncing to database...');
try {
// Get all current Firebase users
const listUsersResult = await firebaseAdmin.auth().listUsers(1000);
existingFirebaseUsers = listUsersResult.users;
console.log(
`Found ${existingFirebaseUsers.length} existing Firebase users`
);
} catch (error) {
console.error('Error listing Firebase users:', error);
existingFirebaseUsers = [];
}
// If we have existing Firebase users, create database records for them
if (existingFirebaseUsers.length > 0) {
return await syncExistingFirebaseUsers(
prisma,
existingFirebaseUsers,
universityId
);
} else {
// If no existing users, create new ones
console.log(
'No existing Firebase users found. Creating from scratch...'
);
return await createAllUsersFromScratch(prisma, universityId);
}
}
} catch (error) {
console.error('Error seeding users:', error);
throw error;
}
};
// Helper function to create users from scratch
async function createAllUsersFromScratch(
prisma: PrismaClient,
universityId: string
): Promise<UserSeedResult> {
console.log('Creating new users from scratch...');
// Create admin user
const adminUser = await createAdminUser(prisma);
// Set up arrays to store user data
const fasilitatorFirebaseUsers: UserRecord[] = [];
const koasFirebaseUsers: UserRecord[] = [];
const pasienFirebaseUsers: UserRecord[] = [];
const koasData: KoasData[] = [];
// Create users
const fasilitatorCountToCreate = 5;
const pasienCountToCreate = 10;
await createFasilitators(fasilitatorCountToCreate, fasilitatorFirebaseUsers);
await createKoasFromData(koasFirebaseUsers, koasData); // Ganti ke fungsi baru
await createPatients(pasienCountToCreate, pasienFirebaseUsers);
// Create database records
await createFasilitatorRecords(prisma, fasilitatorFirebaseUsers);
await createKoasRecords(prisma, koasFirebaseUsers, koasData, universityId);
await createPatientRecords(prisma, pasienFirebaseUsers);
console.log('User creation complete!');
console.log(
`Created ${fasilitatorFirebaseUsers.length} facilitators, ${koasFirebaseUsers.length} koas, and ${pasienFirebaseUsers.length} patient accounts`
);
return {
admin: adminUser,
fasilitatorCount: fasilitatorFirebaseUsers.length,
koasCount: koasFirebaseUsers.length,
pasienCount: pasienFirebaseUsers.length,
};
}
// Create admin user if none exists
async function createAdminUser(prisma: PrismaClient) {
let adminFirebase;
try {
// Check if admin already exists
adminFirebase = await firebaseAdmin
.auth()
.getUserByEmail('admin@dentakoas.com');
console.log('Admin user exists in Firebase');
} catch (error) {
// Create new admin if not exists
adminFirebase = await firebaseAdmin.auth().createUser({
email: 'admin@dentakoas.com',
password: 'Password123',
displayName: 'Admin User',
photoURL: getGenderBasedAvatar('male'), // Default to male for admin
emailVerified: true,
});
await firebaseAdmin
.auth()
.setCustomUserClaims(adminFirebase.uid, { role: 'Admin' });
console.log('Created new admin user in Firebase');
}
// Create admin record in database
const phoneNumber = generatePhoneNumber();
const adminUser = await prisma.user.create({
data: {
id: adminFirebase.uid,
givenName: 'Admin',
familyName: 'User',
name: 'Admin User',
email: 'admin@dentakoas.com',
password: adminFirebase.passwordHash,
role: 'Admin',
image: adminFirebase.photoURL || getGenderBasedAvatar('male'),
phone: phoneNumber,
address: 'Jl. Kalimantan No.37, Jember',
emailVerified: new Date(),
},
});
console.log('Created admin record in database');
return adminUser;
}
// Create admin from existing Firebase user
async function createExistingAdminRecord(
prisma: PrismaClient,
adminFirebase: UserRecord
) {
const phoneNumber = generatePhoneNumber();
const adminUser = await prisma.user.create({
data: {
id: adminFirebase.uid,
givenName: 'Admin',
familyName: 'User',
name: adminFirebase.displayName || 'Admin User',
email: adminFirebase.email || 'admin@dentakoas.com',
password: adminFirebase.passwordHash,
role: 'Admin',
image: adminFirebase.photoURL || getGenderBasedAvatar('male'),
phone: phoneNumber,
address: 'Jl. Kalimantan No.37, Jember',
emailVerified: new Date(),
},
});
console.log('Created admin record in database from existing Firebase user');
return adminUser;
}
// Create facilitator users in Firebase
async function createFasilitators(count: number, userList: UserRecord[]) {
for (let i = 0; i < count; i++) {
// Determine gender randomly
const gender = faker.helpers.arrayElement(['male', 'female']);
const firstName = faker.person.firstName(gender as any);
const lastName = faker.person.lastName();
const email = faker.internet
.email({ firstName, lastName, provider: 'unej.ac.id' })
.toLowerCase();
// Add index to name to ensure uniqueness
const displayName = generateUniqueName(`Dr. ${firstName}`, lastName, i);
const firebaseUser = await firebaseAdmin.auth().createUser({
email: email,
password: 'Password123',
displayName: displayName,
photoURL: getGenderBasedAvatar(gender),
emailVerified: true,
});
await firebaseAdmin
.auth()
.setCustomUserClaims(firebaseUser.uid, { role: 'Fasilitator', gender });
userList.push(firebaseUser);
}
}
// Create koas users in Firebase
async function createKoasFromData(
userList: UserRecord[],
koasDataList: KoasData[]
) {
// Cek email duplikat dan skip jika sudah pernah diproses
const emailSet = new Set<string>();
for (const koas of koasUser) {
const email = koas.email.trim().toLowerCase();
if (emailSet.has(email)) {
console.warn(
`SKIP: Duplicate email detected for ${koas.nama} (${email})`
);
continue;
}
emailSet.add(email);
// Normalisasi gender
const gender = koas.gender?.toLowerCase().includes('laki')
? 'male'
: 'female';
const displayName = koas.nama;
const koasNumber = koas.koas_number;
const entryYear =
typeof koas.entry_year === 'number' ? koas.entry_year : 2023;
const photoURL = koas.img;
const phone = koas.phone;
try {
const firebaseUser = await firebaseAdmin.auth().createUser({
email: email,
password: 'Password123',
displayName: displayName,
photoURL: photoURL || getGenderBasedAvatar(gender),
emailVerified: true,
});
await firebaseAdmin
.auth()
.setCustomUserClaims(firebaseUser.uid, { role: 'Koas', gender });
userList.push(firebaseUser);
koasDataList.push({
id: firebaseUser.uid,
koasNumber: koasNumber,
entryYear: entryYear,
gender: gender,
phone: phone,
});
} catch (err) {
console.error(
`Error creating Firebase user for koas: ${displayName} (${email})`
);
throw err;
}
}
}
// Create patient users in Firebase
async function createPatients(count: number, userList: UserRecord[]) {
const usedNames = new Set<string>();
for (let i = 0; i < count; i++) {
// Determine gender randomly
const gender = faker.helpers.arrayElement(['male', 'female']);
const firstName = faker.person.firstName(gender as any);
const lastName = faker.person.lastName();
// Create a unique name with index suffix if needed
let displayName = `${firstName} ${lastName}`;
let nameIndex = 0;
// If name already exists, add a number suffix
while (usedNames.has(displayName.toLowerCase())) {
nameIndex++;
displayName = generateUniqueName(firstName, lastName, nameIndex);
}
usedNames.add(displayName.toLowerCase());
const email = faker.internet.email({ firstName, lastName }).toLowerCase();
const firebaseUser = await firebaseAdmin.auth().createUser({
email: email,
password: 'Password123',
displayName: displayName,
photoURL: getGenderBasedAvatar(gender),
emailVerified: true,
});
await firebaseAdmin
.auth()
.setCustomUserClaims(firebaseUser.uid, { role: 'Pasien', gender });
userList.push(firebaseUser);
}
}
// Helper function to sync existing Firebase users to database
async function syncExistingFirebaseUsers(
prisma: PrismaClient,
existingFirebaseUsers: UserRecord[],
universityId: string
): Promise<UserSeedResult> {
console.log('Syncing existing Firebase users to database...');
// Track used names to ensure uniqueness
const usedNames = new Set<string>();
// Group users by role (from custom claims)
const adminUsers: UserRecord[] = [];
const fasilitatorUsers: UserRecord[] = [];
const koasUsers: UserRecord[] = [];
const pasienUsers: UserRecord[] = [];
const koasData: KoasData[] = [];
// First pass to collect all existing names
for (const user of existingFirebaseUsers) {
if (user.displayName) {
usedNames.add(user.displayName.toLowerCase());
}
}
// Process users by role
for (let user of existingFirebaseUsers) {
const customClaims = user.customClaims as
| { role?: string; gender?: string }
| undefined;
const role = customClaims?.role || 'Pasien';
// Ensure user has a unique display name
if (!user.displayName || user.displayName.trim() === '') {
// Generate a display name if none exists
const nameParts = user.email?.split('@')[0].split('.') || ['User'];
const firstName =
nameParts[0]?.charAt(0).toUpperCase() + nameParts[0]?.slice(1) ||
'User';
const lastName =
nameParts[1]?.charAt(0).toUpperCase() + nameParts[1]?.slice(1) || '';
// Create a base display name
let displayName = `${firstName}${lastName ? ' ' + lastName : ''}`;
let nameIndex = 0;
// Ensure uniqueness
while (usedNames.has(displayName.toLowerCase())) {
nameIndex++;
displayName = `${firstName}${
lastName ? ' ' + lastName : ''
} ${nameIndex}`;
}
usedNames.add(displayName.toLowerCase());
// Update Firebase user with the new display name
await firebaseAdmin.auth().updateUser(user.uid, {
displayName: displayName,
});
// Update our local user record
user = await firebaseAdmin.auth().getUser(user.uid);
}
switch (role) {
case 'Admin':
adminUsers.push(user);
break;
case 'Fasilitator':
fasilitatorUsers.push(user);
break;
case 'Koas':
koasUsers.push(user);
// Generate koas number if syncing existing users
const entryYear = faker.helpers.arrayElement([23, 24]);
const koasNumber = `${entryYear}1611${faker.string.numeric(6)}`;
koasData.push({
id: user.uid,
koasNumber: koasNumber,
entryYear: entryYear,
});
break;
default: // Pasien or unknown roles
pasienUsers.push(user);
break;
}
}
console.log(
`Categorized users: ${adminUsers.length} admins, ${fasilitatorUsers.length} facilitators, ${koasUsers.length} koas, ${pasienUsers.length} patients`
);
// Create database records for each user type
let adminUser = null;
if (adminUsers.length > 0) {
adminUser = await createExistingAdminRecord(prisma, adminUsers[0]);
} else {
// Create admin if none exists
adminUser = await createAdminUser(prisma);
}
await createFasilitatorRecords(prisma, fasilitatorUsers);
await createKoasRecords(prisma, koasUsers, koasData, universityId);
await createPatientRecords(prisma, pasienUsers);
console.log('User synchronization complete!');
return {
admin: adminUser,
fasilitatorCount: fasilitatorUsers.length,
koasCount: koasUsers.length,
pasienCount: pasienUsers.length,
};
}
// Create facilitator records in database
async function createFasilitatorRecords(
prisma: PrismaClient,
users: UserRecord[]
) {
// Skip if no users
if (users.length === 0) return;
// Create user records one by one to handle potential duplicates
for (const fbUser of users) {
try {
// Determine gender from claims
const customClaims = fbUser.customClaims as
| { gender?: string }
| undefined;
const gender = customClaims?.gender || 'male';
const phoneNumber = generatePhoneNumber();
await prisma.user.create({
data: {
id: fbUser.uid,
givenName: fbUser.displayName?.split(' ')[1] || '', // Skip "Dr." prefix
familyName: fbUser.displayName?.split(' ').slice(2).join(' ') || '',
name: fbUser.displayName || 'User' + fbUser.uid.substring(0, 6), // Ensure uniqueness
email: fbUser.email || '',
emailVerified: new Date(),
password: fbUser.passwordHash,
role: 'Fasilitator',
image: fbUser.photoURL || getGenderBasedAvatar(gender),
phone: phoneNumber,
address: faker.location.streetAddress(),
},
});
// Create facilitator profile
await prisma.fasilitatorProfile.create({
data: {
userId: fbUser.uid,
university: 'Universitas Negeri Jember',
},
});
} catch (error) {
console.error(
`Error creating Fasilitator record for ${fbUser.uid}:`,
error
);
}
}
}
// Create koas records in database
async function createKoasRecords(
prisma: PrismaClient,
users: UserRecord[],
koasData: KoasData[],
universityId: string
) {
if (users.length === 0) return;
for (const fbUser of users) {
try {
// Ambil data koas dari koasData
const koasInfo = koasData.find((k) => k.id === fbUser.uid);
// Fallback jika tidak ada
const gender = koasInfo?.gender;
const phone = koasInfo?.phone;
const koasNumber = koasInfo?.koasNumber || '';
const entryYear = koasInfo?.entryYear || 2023;
const nama = fbUser.displayName!;
// Split nama untuk givenName dan familyName
const nameParts = nama.split(' ');
const givenName = nameParts[0] || '';
const familyName = nameParts.slice(1).join(' ') || '';
// Insert ke tabel User
await prisma.user.create({
data: {
id: fbUser.uid,
givenName: givenName,
familyName: familyName,
name: nama,
email: fbUser.email || '',
emailVerified: new Date(),
password: fbUser.passwordHash,
role: 'Koas',
phone: phone,
address: faker.location.streetAddress(),
},
});
// Insert ke tabel KoasProfile
await prisma.koasProfile.create({
data: {
userId: fbUser.uid,
koasNumber: koasNumber,
age: entryYear ? entryYear.toString() : undefined,
gender: gender === 'female' ? 'Female' : 'Male',
departement: 'Profesi Dokter Gigi',
university: 'Universitas Negeri Jember',
universityId: universityId,
bio: generateShortBio(),
whatsappLink: phone ? generateWhatsAppLink(phone) : '',
status: 'Approved' as StatusKoas,
experience: faker.helpers.rangeToNumber({ min: 0, max: 5 }),
},
});
} catch (error) {
console.error(`Error creating Koas record for ${fbUser.uid}:`, error);
}
}
}
// Create patient records in database
async function createPatientRecords(prisma: PrismaClient, users: UserRecord[]) {
// Skip if no users
if (users.length === 0) return;
// Create user records one by one to handle potential duplicates
for (const fbUser of users) {
try {
// Extract gender from claims
const customClaims = fbUser.customClaims as
| { gender?: string }
| undefined;
const gender = customClaims?.gender || 'male';
const phoneNumber = generatePhoneNumber();
// Create user record
await prisma.user.create({
data: {
id: fbUser.uid,
givenName: fbUser.displayName?.split(' ')[0] || '',
familyName: fbUser.displayName?.split(' ').slice(1).join(' ') || '',
name: fbUser.displayName || 'User' + fbUser.uid.substring(0, 6), // Ensure uniqueness
email: fbUser.email || '',
emailVerified: new Date(),
password:
'$2a$10$Ab6y1GO/BF.MMuZ97zj9B.S2s8wVEMRFXDn8GK9tmlpHt.T2HzYHW',
role: 'Pasien',
image: fbUser.photoURL || getGenderBasedAvatar(gender),
phone: phoneNumber,
address: faker.location.streetAddress(),
},
});
// Create patient profile
await prisma.pasienProfile.create({
data: {
userId: fbUser.uid,
age: faker.helpers.rangeToNumber({ min: 18, max: 70 }).toString(),
gender: gender === 'female' ? 'Female' : 'Male',
bio: generateShortBio(),
},
});
} catch (error) {
console.error(`Error creating Pasien record for ${fbUser.uid}:`, error);
}
}
}
// Helper function to generate a hash from a string (for consistent avatar IDs)
function hashString(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash);
}