import { eq, and, sql } from "drizzle-orm"; import { drizzle } from "drizzle-orm/mysql2"; import { InsertUser, users, formations, apprenants, sequences, datesFormation, inscriptions, passwordResetTokens, InsertPasswordResetToken, InsertFormation, InsertApprenant, InsertSequence, InsertDateFormation, InsertInscription, Sequence, DateFormation, Apprenant, Formation } from "../drizzle/schema"; import { ENV } from './_core/env'; let _db: ReturnType | null = null; // Lazily create the drizzle instance so local tooling can run without a DB. export async function getDb() { if (!_db && process.env.DATABASE_URL) { try { _db = drizzle(process.env.DATABASE_URL); } catch (error) { console.warn("[Database] Failed to connect:", error); _db = null; } } return _db; } export async function upsertUser(user: InsertUser): Promise { if (!user.openId) { throw new Error("User openId is required for upsert"); } const db = await getDb(); if (!db) { console.warn("[Database] Cannot upsert user: database not available"); return; } try { const values: InsertUser = { openId: user.openId, }; const updateSet: Record = {}; const textFields = ["name", "email", "loginMethod"] as const; type TextField = (typeof textFields)[number]; const assignNullable = (field: TextField) => { const value = user[field]; if (value === undefined) return; const normalized = value ?? null; values[field] = normalized; updateSet[field] = normalized; }; textFields.forEach(assignNullable); if (user.lastSignedIn !== undefined) { values.lastSignedIn = user.lastSignedIn; updateSet.lastSignedIn = user.lastSignedIn; } if (user.role !== undefined) { values.role = user.role; updateSet.role = user.role; } else if (user.openId === ENV.ownerOpenId) { values.role = 'admin'; updateSet.role = 'admin'; } if (!values.lastSignedIn) { values.lastSignedIn = new Date(); } if (Object.keys(updateSet).length === 0) { updateSet.lastSignedIn = new Date(); } await db.insert(users).values(values).onDuplicateKeyUpdate({ set: updateSet, }); } catch (error) { console.error("[Database] Failed to upsert user:", error); throw error; } } export async function getUserByOpenId(openId: string) { const db = await getDb(); if (!db) { console.warn("[Database] Cannot get user: database not available"); return undefined; } const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1); return result.length > 0 ? result[0] : undefined; } // ==================== FORMATIONS ==================== export async function createFormation(data: InsertFormation) { const db = await getDb(); if (!db) throw new Error("Database not available"); const result = await db.insert(formations).values(data); return result; } export async function getFormations() { const db = await getDb(); if (!db) return []; return await db.select().from(formations); } export async function getFormationById(id: number) { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(formations).where(eq(formations.id, id)).limit(1); return result.length > 0 ? result[0] : undefined; } export async function getFormationByLien(lien: string) { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(formations).where(eq(formations.lienUnique, lien)).limit(1); return result.length > 0 ? result[0] : undefined; } export async function updateFormation(id: number, data: Partial) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.update(formations).set(data).where(eq(formations.id, id)); } export async function deleteFormation(id: number) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.delete(formations).where(eq(formations.id, id)); } // ==================== SÉQUENCES ==================== export async function createSequence(data: InsertSequence) { const db = await getDb(); if (!db) throw new Error("Database not available"); const result = await db.insert(sequences).values(data); return result; } export async function getSequences() { const db = await getDb(); if (!db) return []; return await db.select().from(sequences); } export async function getSequenceById(id: number): Promise { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(sequences).where(eq(sequences.id, id)).limit(1); return result.length > 0 ? result[0] : undefined; } export async function getSequencesByFormation(formationId: number) { const db = await getDb(); if (!db) return []; return await db.select().from(sequences).where(eq(sequences.formationId, formationId)); } export async function updateSequence(id: number, data: Partial) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.update(sequences).set(data).where(eq(sequences.id, id)); } export async function deleteSequence(id: number) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.delete(sequences).where(eq(sequences.id, id)); } // ==================== DATES DE FORMATION ==================== export async function createDateFormation(data: InsertDateFormation) { const db = await getDb(); if (!db) throw new Error("Database not available"); const result = await db.insert(datesFormation).values(data); return result; } export async function getDatesBySequence(sequenceId: number): Promise { const db = await getDb(); if (!db) return []; const results = await db.select() .from(datesFormation) .where(eq(datesFormation.sequenceId, sequenceId)) .orderBy(datesFormation.ordre); return results; } export async function deleteDatesBySequence(sequenceId: number) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.delete(datesFormation).where(eq(datesFormation.sequenceId, sequenceId)); } // ==================== APPRENANTS ==================== export async function createApprenant(data: InsertApprenant) { const db = await getDb(); if (!db) throw new Error("Database not available"); const result = await db.insert(apprenants).values(data); return result; } export async function getApprenants() { const db = await getDb(); if (!db) return []; return await db.select().from(apprenants); } export async function getApprenantById(id: number): Promise { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(apprenants).where(eq(apprenants.id, id)).limit(1); return result.length > 0 ? result[0] : undefined; } export async function getApprenantByEmail(email: string) { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(apprenants).where(eq(apprenants.email, email)).limit(1); return result.length > 0 ? result[0] : undefined; } export async function updateApprenant(id: number, data: Partial) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.update(apprenants).set(data).where(eq(apprenants.id, id)); } export async function deleteApprenant(id: number) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.delete(apprenants).where(eq(apprenants.id, id)); } // ==================== INSCRIPTIONS ==================== export async function createInscription(data: InsertInscription) { const db = await getDb(); if (!db) throw new Error("Database not available"); const result = await db.insert(inscriptions).values(data); return result; } export async function getAllInscriptions() { const db = await getDb(); if (!db) return []; const results = await db.select({ inscription: inscriptions, apprenant: apprenants, }) .from(inscriptions) .leftJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id)); return results; } export async function getInscriptionsBySequence(sequenceId: number) { const db = await getDb(); if (!db) return []; const results = await db.select({ inscription: inscriptions, apprenant: apprenants, }) .from(inscriptions) .leftJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id)) .where(eq(inscriptions.sequenceId, sequenceId)); return results; } export async function getInscriptionsByApprenant(apprenantId: number) { const db = await getDb(); if (!db) return []; const results = await db.select({ inscription: inscriptions, sequence: sequences, }) .from(inscriptions) .leftJoin(sequences, eq(inscriptions.sequenceId, sequences.id)) .where(eq(inscriptions.apprenantId, apprenantId)); return results; } export async function checkExistingInscription(apprenantId: number, sequenceId: number) { const db = await getDb(); if (!db) return undefined; const result = await db.select() .from(inscriptions) .where(and( eq(inscriptions.apprenantId, apprenantId), eq(inscriptions.sequenceId, sequenceId) )) .limit(1); return result.length > 0 ? result[0] : undefined; } export async function countInscriptionsBySequence(sequenceId: number, statut?: string) { const db = await getDb(); if (!db) return 0; const conditions = [eq(inscriptions.sequenceId, sequenceId)]; if (statut) { conditions.push(eq(inscriptions.statut, statut as any)); } const result = await db.select({ count: sql`count(*)` }) .from(inscriptions) .where(and(...conditions)); return result[0]?.count || 0; } export async function updateInscription(id: number, data: Partial) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.update(inscriptions).set(data).where(eq(inscriptions.id, id)); } export async function deleteInscription(id: number) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.delete(inscriptions).where(eq(inscriptions.id, id)); } // ==================== GESTION DES UTILISATEURS ==================== export async function createUser(data: InsertUser) { const db = await getDb(); if (!db) throw new Error("Database not available"); const result = await db.insert(users).values(data); return result; } export async function getAllUsers() { const db = await getDb(); if (!db) return []; return await db.select().from(users); } export async function getUserById(id: number) { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(users).where(eq(users.id, id)).limit(1); return result.length > 0 ? result[0] : undefined; } export async function updateUser(id: number, data: Partial) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.update(users).set(data).where(eq(users.id, id)); } export async function toggleUserStatus(id: number, isActive: boolean) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.update(users).set({ isActive }).where(eq(users.id, id)); } export async function deleteUser(id: number) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.delete(users).where(eq(users.id, id)); } // ==================== GESTION DES TOKENS DE RÉINITIALISATION ==================== export async function createPasswordResetToken(userId: number, token: string, expiresAt: Date) { const db = await getDb(); if (!db) throw new Error("Database not available"); const result = await db.insert(passwordResetTokens).values({ userId, token, expiresAt, used: false, }); return result; } export async function getPasswordResetToken(token: string) { const db = await getDb(); if (!db) return undefined; const result = await db.select() .from(passwordResetTokens) .where(eq(passwordResetTokens.token, token)) .limit(1); return result.length > 0 ? result[0] : undefined; } export async function markTokenAsUsed(tokenId: number) { const db = await getDb(); if (!db) throw new Error("Database not available"); await db.update(passwordResetTokens) .set({ used: true }) .where(eq(passwordResetTokens.id, tokenId)); } export async function deleteExpiredTokens() { const db = await getDb(); if (!db) throw new Error("Database not available"); const now = new Date(); await db.delete(passwordResetTokens) .where(sql`${passwordResetTokens.expiresAt} < ${now}`); }