import "dotenv/config"; import { createClient } from "@supabase/supabase-js"; import { clerkClient } from "@clerk/nextjs/server"; import * as fs from "fs"; import * as path from "path"; const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, "sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz", { auth: { persistSession: false } } ); const PASSWORD = "&%5C400l&%"; async function cleanClerk() { console.log("Cleaning up old Clerk users..."); const clerk = clerkClient(); const users = await clerk.users.getUserList({ limit: 500 }); for (const user of users.data) { if ( user.publicMetadata.role === "teacher" || user.publicMetadata.role === "student" || user.publicMetadata.role === "parent" ) { await clerk.users.deleteUser(user.id); } } } async function cleanSupabase() { console.log("Cleaning up Supabase tables..."); const tables = [ "Result", "Assignment", "Exam", "Attendance", "Event", "Announcement", "Lesson", "TeacherSchool", "TeacherSubject", "StudentClass", "Student", "Teacher", "Parent", "Class", "Subject", "Grade", "School", "Admin" ]; for (const table of tables) { await supabase.from(table).delete().neq("id", "0" as any); } } type AdminSeedInfo = { adminId: string; defaultSchoolId: string; }; async function seedAdmin(): Promise { console.log("Syncing Admin and Creating Default School..."); const clerk = clerkClient(); const users = await clerk.users.getUserList({ limit: 100 }); const adminUser = users.data.find(u => u.username === "admin" || u.emailAddresses[0]?.emailAddress?.includes("admin")); if (adminUser) { const defaultSchoolId = "default-school-1"; await supabase.from("School").upsert({ id: defaultSchoolId, name: "Lama Academy", type: "MANAGED", adminId: adminUser.id }); await supabase.from("Admin").upsert({ id: adminUser.id, username: adminUser.username || "admin", schoolId: defaultSchoolId, }); console.log( `Synced Admin ID: ${adminUser.id} and School ID: ${defaultSchoolId}`, ); return { adminId: adminUser.id, defaultSchoolId }; } return null; } /** Parse a CSV line respecting double-quoted fields (handles commas inside quotes). */ function parseCsvLine(line: string): string[] { const trimmed = line.replace(/\r$/, "").trim(); if (!trimmed) return []; const result: string[] = []; let current = ""; let inQuotes = false; for (let i = 0; i < trimmed.length; i++) { const c = trimmed[i]; if (c === '"') { if (inQuotes && trimmed[i + 1] === '"') { current += '"'; i++; } else { inQuotes = !inQuotes; } } else if (c === "," && !inQuotes) { result.push(current.trim()); current = ""; } else { current += c; } } result.push(current.trim()); return result; } async function seedSchoolDirectory(adminId: string) { try { console.log("Seeding School directory from school_data.csv..."); const csvPath = path.join(process.cwd(), "school_data.csv"); if (!fs.existsSync(csvPath)) { console.log("school_data.csv not found. Skipping school directory seed."); return; } const raw = fs.readFileSync(csvPath, "utf-8"); const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0); if (lines.length <= 1) { console.log("school_data.csv has no data rows. Skipping."); return; } const header = parseCsvLine(lines[0]); const idxUrn = header.indexOf("URN"); const idxName = header.indexOf("EstablishmentName"); const idxStatus = header.indexOf("EstablishmentStatus (name)"); const idxTypeGroup = header.indexOf("EstablishmentTypeGroup (name)"); if (idxUrn === -1 || idxName === -1 || idxStatus === -1 || idxTypeGroup === -1) { console.log("Expected columns not found in school_data.csv header. Skipping."); return; } const schools: { id: string; name: string; type: string; adminId: string }[] = []; const MAX_ROWS = 500; // limit for local dev to avoid huge inserts const dataLines = lines.slice(1, 1 + MAX_ROWS); for (const line of dataLines) { const cols = parseCsvLine(line); if (!cols.length) continue; const status = cols[idxStatus]; if (status !== "Open") continue; const urn = cols[idxUrn]; const name = cols[idxName]; const typeGroup = cols[idxTypeGroup] || ""; if (!urn || !name) continue; const schoolType = /independent/i.test(typeGroup) ? "INDEPENDENT" : "MANAGED"; schools.push({ id: urn.toString(), name, type: schoolType, adminId, }); } if (schools.length === 0) { console.log("No open schools found to seed from CSV."); return; } const { error } = await supabase.from("School").upsert(schools); if (error) { console.error("Error seeding School directory from CSV:", error); } else { console.log(`Seeded/updated ${schools.length} schools from directory.`); } } catch (err) { console.error("Failed to seed School directory from CSV:", err); } } async function seedTestTimetableForSchool( schoolId: string, teacherId: string, classId: number, subjectIds: number[], ) { try { console.log(`Seeding basic timetable config for school ${schoolId}...`); // 1. Ensure AcademicYear and SchoolTimetable exist for this school const { data: existingAy } = await supabase .from("AcademicYear") .select("id") .eq("schoolId", schoolId) .limit(1) .single(); let academicYearId: number; if (existingAy?.id) { academicYearId = existingAy.id; } else { const { data: newAy, error: ayError } = await supabase .from("AcademicYear") .insert({ schoolId, startYear: 2026, endYear: 2027, name: "2026-2027" }) .select("id") .single(); if (ayError || !newAy) { console.error("Error seeding AcademicYear for school", schoolId, ayError); return; } academicYearId = newAy.id; } const { data: existingSt } = await supabase .from("SchoolTimetable") .select("id") .eq("academicYearId", academicYearId) .eq("schoolId", schoolId) .limit(1) .single(); let schoolTimetableId: number; if (existingSt?.id) { schoolTimetableId = existingSt.id; } else { const { data: newSt, error: stError } = await supabase .from("SchoolTimetable") .insert({ academicYearId, schoolId, name: "Standard Week" }) .select("id") .single(); if (stError || !newSt) { console.error("Error seeding SchoolTimetable for school", schoolId, stError); return; } schoolTimetableId = newSt.id; } const termStart = new Date("2026-02-23T00:00:00.000Z"); const termEnd = new Date("2026-03-27T00:00:00.000Z"); const { data: term, error: termError } = await supabase .from("Term") .insert({ schoolId, academicYearId, name: "Spring Term 2026 (Seeded)", startDate: termStart.toISOString(), endDate: termEnd.toISOString(), }) .select() .single(); if (termError) { console.error("Error seeding term for school", schoolId, termError); return; } await supabase.from("Holiday").insert({ schoolId, academicYearId, name: "Half Term Break", startDate: "2026-03-09T00:00:00.000Z", endDate: "2026-03-13T23:59:59.000Z", }); const { data: slots, error: slotError } = await supabase .from("SchoolTimetableSlot") .insert([ { schoolId, schoolTimetableId, name: "Period 1", startTime: "09:00", endTime: "10:00", isTeachingSlot: true, position: 1, }, { schoolId, schoolTimetableId, name: "Period 2", startTime: "10:15", endTime: "11:15", isTeachingSlot: true, position: 2, }, { schoolId, schoolTimetableId, name: "Period 3", startTime: "11:30", endTime: "12:30", isTeachingSlot: true, position: 3, }, ]) .select(); if (slotError || !slots || slots.length === 0) { console.error("Error seeding timetable slots for school", schoolId, slotError); return; } const p1 = slots.find((s: any) => s.name === "Period 1"); const p2 = slots.find((s: any) => s.name === "Period 2"); const p3 = slots.find((s: any) => s.name === "Period 3"); if (!p1 || !p2 || !p3) { console.error("Missing seeded slots for school", schoolId); return; } const { data: templates, error: templateError } = await supabase .from("TeacherTimetableTemplate") .insert({ schoolId, schoolTimetableId, name: "Default Weekly Template (Seeded)", teacherId, }) .select(); if (templateError || !templates || templates.length === 0) { console.error("Error seeding timetable template for school", schoolId, templateError); return; } const templateId = templates[0].id as number; const [subjectA, subjectB] = subjectIds; const entries = [ { teacherTimetableTemplateId: templateId, schoolTimetableSlotId: p1.id, classId, subjectId: subjectA, dayOfWeek: 1, }, { teacherTimetableTemplateId: templateId, schoolTimetableSlotId: p2.id, classId, subjectId: subjectB, dayOfWeek: 1, }, { teacherTimetableTemplateId: templateId, schoolTimetableSlotId: p1.id, classId, subjectId: subjectB, dayOfWeek: 3, }, { teacherTimetableTemplateId: templateId, schoolTimetableSlotId: p3.id, classId, subjectId: subjectA, dayOfWeek: 5, }, ]; const { error: entryError } = await supabase .from("TeacherTimetableEntry") .insert(entries); if (entryError) { console.error("Error seeding timetable entries for school", schoolId, entryError); } } catch (err) { console.error("Failed to seed timetable config for school", schoolId, err); } } async function main() { try { const clerk = clerkClient(); await cleanClerk(); await cleanSupabase(); const adminInfo = await seedAdmin(); if (!adminInfo) { console.error("No Admin user found. Seed aborted."); return; } const { adminId, defaultSchoolId } = adminInfo; // Populate School directory from CSV (for My Schools / selection) await seedSchoolDirectory(adminId); console.log("Seeding Grades and Subjects..."); const grades = [1, 2, 3, 4, 5, 6].map((level) => ({ id: level, level })); await supabase.from("Grade").insert(grades); const subjectsArray = [ { id: 1, name: "Mathematics", schoolId: defaultSchoolId }, { id: 2, name: "Science", schoolId: defaultSchoolId }, { id: 3, name: "English", schoolId: defaultSchoolId }, { id: 4, name: "History", schoolId: defaultSchoolId }, { id: 5, name: "Geography", schoolId: defaultSchoolId }, { id: 6, name: "Physics", schoolId: defaultSchoolId }, { id: 7, name: "Chemistry", schoolId: defaultSchoolId }, { id: 8, name: "Biology", schoolId: defaultSchoolId }, { id: 9, name: "Computer Science", schoolId: defaultSchoolId }, { id: 10, name: "Art", schoolId: defaultSchoolId }, ]; await supabase.from("Subject").insert(subjectsArray); console.log("Creating 15 Managed Teachers in Default School..."); const teacherMap: Record = {}; for (let i = 1; i <= 15; i++) { const user = await clerk.users.createUser({ username: `teacher${i}`, password: PASSWORD, firstName: `TName${i}`, lastName: `TSurname${i}`, publicMetadata: { role: "teacher" } }); teacherMap[i] = user.id; await supabase.from("Teacher").insert({ id: user.id, username: `teacher${i}`, name: `TName${i}`, surname: `TSurname${i}`, email: `teacher${i}@example.com`, phone: `123-456-789${i}`, address: `Address${i}`, bloodType: "A+", sex: i % 2 === 0 ? "MALE" : "FEMALE", birthday: "1996-02-27T00:26:35.280Z" }); await supabase.from("TeacherSchool").insert({ teacherId: user.id, schoolId: defaultSchoolId, isManaged: true }); await supabase.from("TeacherSubject").insert([ { subjectId: (i % 10) + 1, teacherId: user.id, isPrimary: true }, { subjectId: ((i + 1) % 10) + 1, teacherId: user.id } ]); } console.log("Creating Independent Teacher & Test School..."); const independentUser = await clerk.users.createUser({ username: "independent1", password: PASSWORD, firstName: "Indy", lastName: "Teacher", publicMetadata: { role: "teacher", teacherType: "INDEPENDENT" }, }); await supabase.from("Teacher").insert({ id: independentUser.id, username: "independent1", name: "Indy", surname: "Teacher", email: "independent1@example.com", phone: "123-456-7890", address: "Independent Street 1", bloodType: "A+", sex: "FEMALE", birthday: "1990-01-01T00:00:00.000Z", }); const independentSchoolId = "independent-school-1"; await supabase.from("School").upsert({ id: independentSchoolId, name: "Independent School 1", type: "INDEPENDENT", adminId: independentUser.id, }); await supabase.from("TeacherSchool").insert({ teacherId: independentUser.id, schoolId: independentSchoolId, isManaged: false, }); // Optional: basic schedule indicating this teacher works at their independent school on weekdays await supabase.from("TeacherSchoolSchedule").insert({ teacherId: independentUser.id, schoolId: independentSchoolId, startDate: "2026-01-01T00:00:00.000Z", endDate: "2026-12-31T00:00:00.000Z", daysOfWeek: [1, 2, 3, 4, 5], }); const independentSubjects = [ { id: 1001, name: "Indy Mathematics", schoolId: independentSchoolId }, { id: 1002, name: "Indy English", schoolId: independentSchoolId }, ]; await supabase.from("Subject").insert(independentSubjects); await supabase.from("TeacherSubject").insert([ { subjectId: 1001, teacherId: independentUser.id, isPrimary: true }, { subjectId: 1002, teacherId: independentUser.id }, ]); console.log("Creating Agency Teacher & Test Agency School..."); const agencyUser = await clerk.users.createUser({ username: "agency1", password: PASSWORD, firstName: "Agen", lastName: "Teacher", publicMetadata: { role: "teacher", teacherType: "AGENCY" }, }); await supabase.from("Teacher").insert({ id: agencyUser.id, username: "agency1", name: "Agen", surname: "Teacher", email: "agency1@example.com", phone: "987-654-3210", address: "Agency Road 1", bloodType: "B+", sex: "MALE", birthday: "1988-05-15T00:00:00.000Z", }); const agencySchoolId = "agency-school-1"; await supabase.from("School").upsert({ id: agencySchoolId, name: "Agency School 1", type: "AGENCY", adminId: agencyUser.id, }); await supabase.from("TeacherSchool").insert({ teacherId: agencyUser.id, schoolId: agencySchoolId, isManaged: false, }); await supabase.from("TeacherSchoolSchedule").insert({ teacherId: agencyUser.id, schoolId: agencySchoolId, startDate: "2026-01-01T00:00:00.000Z", endDate: "2026-12-31T00:00:00.000Z", daysOfWeek: [1, 3, 5], // Example: Mon, Wed, Fri }); const agencySubjects = [ { id: 1003, name: "Agency Mathematics", schoolId: agencySchoolId }, { id: 1004, name: "Agency English", schoolId: agencySchoolId }, ]; await supabase.from("Subject").insert(agencySubjects); await supabase.from("TeacherSubject").insert([ { subjectId: 1003, teacherId: agencyUser.id, isPrimary: true }, { subjectId: 1004, teacherId: agencyUser.id }, ]); console.log("Creating additional Independent & Agency test teachers (blank setups)..."); const independentUser2 = await clerk.users.createUser({ username: "independent2", password: PASSWORD, firstName: "Indy", lastName: "Teacher2", publicMetadata: { role: "teacher", teacherType: "INDEPENDENT" }, }); await supabase.from("Teacher").insert({ id: independentUser2.id, username: "independent2", name: "Indy", surname: "Teacher2", email: "independent2@example.com", phone: "123-456-7891", address: "Independent Street 2", bloodType: "A+", sex: "MALE", birthday: "1992-01-01T00:00:00.000Z", }); const independentBlankSchoolId = "independent-school-2"; await supabase.from("School").upsert({ id: independentBlankSchoolId, name: "Independent School 2 (Blank)", type: "INDEPENDENT", adminId: independentUser2.id, }); await supabase.from("TeacherSchool").insert({ teacherId: independentUser2.id, schoolId: independentBlankSchoolId, isManaged: false, }); await supabase.from("TeacherSchoolSchedule").insert({ teacherId: independentUser2.id, schoolId: independentBlankSchoolId, startDate: "2026-01-01T00:00:00.000Z", endDate: "2026-12-31T00:00:00.000Z", daysOfWeek: [1, 2, 3, 4, 5], }); const agencyUser2 = await clerk.users.createUser({ username: "agency2", password: PASSWORD, firstName: "Agen", lastName: "Teacher2", publicMetadata: { role: "teacher", teacherType: "AGENCY" }, }); await supabase.from("Teacher").insert({ id: agencyUser2.id, username: "agency2", name: "Agen", surname: "Teacher2", email: "agency2@example.com", phone: "987-654-3211", address: "Agency Road 2", bloodType: "B+", sex: "FEMALE", birthday: "1989-05-15T00:00:00.000Z", }); const agencyBlankSchoolId = "agency-school-2"; await supabase.from("School").upsert({ id: agencyBlankSchoolId, name: "Agency School 2 (Blank)", type: "AGENCY", adminId: agencyUser2.id, }); await supabase.from("TeacherSchool").insert({ teacherId: agencyUser2.id, schoolId: agencyBlankSchoolId, isManaged: false, }); await supabase.from("TeacherSchoolSchedule").insert({ teacherId: agencyUser2.id, schoolId: agencyBlankSchoolId, startDate: "2026-01-01T00:00:00.000Z", endDate: "2026-12-31T00:00:00.000Z", daysOfWeek: [2, 4], // Example: Tue, Thu }); console.log("Creating 6 Classes for default school..."); const classesArray = [ { id: 1, name: "1A", gradeId: 1, capacity: 20, supervisorId: teacherMap[1], schoolId: defaultSchoolId }, { id: 2, name: "2A", gradeId: 2, capacity: 20, supervisorId: teacherMap[2], schoolId: defaultSchoolId }, { id: 3, name: "3A", gradeId: 3, capacity: 20, supervisorId: teacherMap[3], schoolId: defaultSchoolId }, { id: 4, name: "4A", gradeId: 4, capacity: 20, supervisorId: teacherMap[4], schoolId: defaultSchoolId }, { id: 5, name: "5A", gradeId: 5, capacity: 20, supervisorId: teacherMap[5], schoolId: defaultSchoolId }, { id: 6, name: "6A", gradeId: 6, capacity: 20, supervisorId: teacherMap[1], schoolId: defaultSchoolId }, ]; await supabase.from("Class").insert(classesArray); console.log("Creating test classes for Independent and Agency schools..."); const independentClassId = 1001; const agencyClassId = 2001; await supabase.from("Class").insert([ { id: independentClassId, name: "Indy Class 1", gradeId: 1, capacity: 25, supervisorId: independentUser.id, schoolId: independentSchoolId, }, { id: agencyClassId, name: "Agency Class 1", gradeId: 1, capacity: 25, supervisorId: agencyUser.id, schoolId: agencySchoolId, }, ]); console.log("Seeding basic timetable config for Independent and Agency test schools..."); await seedTestTimetableForSchool( independentSchoolId, independentUser.id, 1001, [1001, 1002], ); await seedTestTimetableForSchool( agencySchoolId, agencyUser.id, 2001, [1003, 1004], ); console.log("Creating 25 Parents..."); const parentMap: Record = {}; for (let i = 1; i <= 25; i++) { const user = await clerk.users.createUser({ username: `parent${i}`, password: PASSWORD, firstName: `PName${i}`, lastName: `PSurname${i}`, publicMetadata: { role: "parent" } }); parentMap[i] = user.id; await supabase.from("Parent").insert({ id: user.id, username: `parent${i}`, name: `PName${i}`, surname: `PSurname${i}`, email: `parent${i}@example.com`, phone: `123-456-789${i}`, address: `Address${i}`, schoolId: defaultSchoolId }); } console.log("Creating 50 Students..."); const studentMap: Record = {}; for (let i = 1; i <= 50; i++) { const user = await clerk.users.createUser({ username: `student${i}`, password: PASSWORD, firstName: `SName${i}`, lastName: `SSurname${i}`, publicMetadata: { role: "student" } }); studentMap[i] = user.id; const classInfo = classesArray[(i % 6)]; await supabase.from("Student").insert({ id: user.id, username: `student${i}`, name: `SName${i}`, surname: `SSurname${i}`, email: `student${i}@example.com`, phone: `987-654-321${i}`, address: `Address${i}`, bloodType: "O-", sex: i % 2 === 0 ? "MALE" : "FEMALE", parentId: parentMap[(i % 25) + 1], gradeId: classInfo.gradeId, birthday: "2016-02-27T00:26:35.281Z", schoolId: defaultSchoolId }); await supabase.from("StudentClass").insert({ studentId: user.id, classId: classInfo.id }); } console.log("Exporting User IDs to seed-data.json..."); const seedDataPath = path.join(process.cwd(), "scripts", "seed-data.json"); const seedData = { teacherMap, parentMap, studentMap, classes: classesArray, defaultSchoolId, independentTeacherId: independentUser.id, independentSchoolId, agencyTeacherId: agencyUser.id, agencySchoolId, }; fs.writeFileSync(seedDataPath, JSON.stringify(seedData, null, 2)); console.log("User generation complete! Saved to seed-data.json."); /* const daysOfWeek: ("MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY")[] = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"]; for (let i = 1; i <= 30; i++) { const classIndex = i % 6; const classInfo = classesArray[classIndex]; const teacherIndex = (i % 15) + 1; const teacherId = teacherMap[teacherIndex]; const subjectId = (teacherIndex % 10) + 1; // Distribute across 5 days (Monday to Friday) const dayIndex = i % 5; const dayName = daysOfWeek[dayIndex]; // Distribute across 6 periods (e.g., 8:00 AM to 2:00 PM) const periodIndex = (i % 6); // Base date (a Monday): 2026-02-23T00:00:00.000Z const lessonDate = new Date("2026-02-23T00:00:00.000Z"); lessonDate.setDate(lessonDate.getDate() + dayIndex); // Create start time and end time (1 hour lesson) const startHour = 8 + periodIndex; const startTime = new Date(lessonDate); startTime.setUTCHours(startHour, 0, 0, 0); const endTime = new Date(lessonDate); endTime.setUTCHours(startHour + 1, 0, 0, 0); await supabase.from("Lesson").insert({ id: i, name: `Lesson${i}`, day: dayName, startTime: startTime.toISOString(), endTime: endTime.toISOString(), subjectId: subjectId, classId: classInfo.id, teacherId: teacherId }); await supabase.from("Exam").insert({ id: i, title: `Exam ${i}`, startTime: startTime.toISOString(), endTime: endTime.toISOString(), lessonId: i }); await supabase.from("Assignment").insert({ id: i, title: `Assignment ${i}`, startDate: startTime.toISOString(), dueDate: new Date(startTime.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(), // Due in 7 days lessonId: i }); // Result requires a student from the class 'classIndex'. // Student 'j' is in class 'j % 6'. const studentBase = classIndex === 0 ? 6 : classIndex; const studentIndex = studentBase + 6 * (i % 8); // Ensures variation 1-48 await supabase.from("Result").insert({ id: i, score: 90 + (i % 10), studentId: studentMap[studentIndex], examId: i }); } */ } catch (err) { console.error("Seed error:", err); } } main();