Files
sonum/server/db.ts

393 lines
14 KiB
TypeScript

import { and, desc, eq, ilike, like, or, sql } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import {
InsertUser,
blocsFonctionnels,
consultations,
demandesContact,
editeurs,
etablissements,
logicielsEtablissements,
solutions,
users,
} from "../drizzle/schema";
import { ENV } from "./_core/env";
let _db: ReturnType<typeof drizzle> | null = null;
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;
}
// ─── Utilisateurs ─────────────────────────────────────────────────────────────
export async function upsertUser(user: InsertUser): Promise<void> {
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; }
const values: InsertUser = { openId: user.openId };
const updateSet: Record<string, unknown> = {};
const textFields = ["name", "email", "loginMethod"] as const;
for (const field of textFields) {
const value = user[field];
if (value === undefined) continue;
const normalized = value ?? null;
values[field] = normalized;
updateSet[field] = normalized;
}
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 });
}
export async function getUserByOpenId(openId: string) {
const db = await getDb();
if (!db) return undefined;
const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
return result.length > 0 ? result[0] : undefined;
}
export async function updateUserCgu(userId: number) {
const db = await getDb();
if (!db) return;
await db.update(users).set({ cguAccepted: true, cguAcceptedAt: new Date() }).where(eq(users.id, userId));
}
export async function updateUserSonumRole(userId: number, sonumRole: "referent" | "gestionnaire") {
const db = await getDb();
if (!db) return;
await db.update(users).set({ sonumRole }).where(eq(users.id, userId));
}
export async function getAllUsers() {
const db = await getDb();
if (!db) return [];
return db.select().from(users).orderBy(desc(users.createdAt));
}
// ─── Référentiel ──────────────────────────────────────────────────────────────
export async function getEditeurs() {
const db = await getDb();
if (!db) return [];
return db.select().from(editeurs).where(eq(editeurs.estValide, true)).orderBy(editeurs.nom);
}
export async function createEditeur(nom: string, estValide = false) {
const db = await getDb();
if (!db) return null;
const result = await db.insert(editeurs).values({ nom, estValide });
return result[0];
}
export async function getBlocsFonctionnels() {
const db = await getDb();
if (!db) return [];
return db.select().from(blocsFonctionnels).where(eq(blocsFonctionnels.estValide, true)).orderBy(blocsFonctionnels.nom);
}
export async function createBlocFonctionnel(nom: string, estValide = false) {
const db = await getDb();
if (!db) return null;
const result = await db.insert(blocsFonctionnels).values({ nom, estValide });
return result[0];
}
export async function getSolutions(search?: string) {
const db = await getDb();
if (!db) return [];
const query = db
.select({
id: solutions.id,
nom: solutions.nom,
editeurId: solutions.editeurId,
editeurNom: editeurs.nom,
blocFonctionnelId: solutions.blocFonctionnelId,
blocFonctionnelNom: blocsFonctionnels.nom,
estValide: solutions.estValide,
})
.from(solutions)
.leftJoin(editeurs, eq(solutions.editeurId, editeurs.id))
.leftJoin(blocsFonctionnels, eq(solutions.blocFonctionnelId, blocsFonctionnels.id))
.where(
search
? and(eq(solutions.estValide, true), or(like(solutions.nom, `%${search}%`), like(editeurs.nom, `%${search}%`)))
: eq(solutions.estValide, true)
)
.orderBy(solutions.nom);
return query;
}
export async function createSolution(nom: string, editeurId: number, blocFonctionnelId?: number | null, estValide = false) {
const db = await getDb();
if (!db) return null;
const result = await db.insert(solutions).values({ nom, editeurId, blocFonctionnelId: blocFonctionnelId ?? null, estValide });
return result[0];
}
// ─── Établissements ───────────────────────────────────────────────────────────
export async function getEtablissementsByReferent(referentId: number) {
const db = await getDb();
if (!db) return [];
return db.select().from(etablissements).where(eq(etablissements.referentId, referentId)).orderBy(etablissements.nom);
}
export async function getAllEtablissements() {
const db = await getDb();
if (!db) return [];
return db.select().from(etablissements).orderBy(etablissements.nom);
}
export async function getEtablissementById(id: number) {
const db = await getDb();
if (!db) return null;
const result = await db.select().from(etablissements).where(eq(etablissements.id, id)).limit(1);
return result[0] ?? null;
}
export async function searchEtablissements(filters: {
solutionId?: number;
editeurId?: number;
blocFonctionnelId?: number;
region?: string;
typeActivite?: string;
tailleEffectifs?: string;
etatDeploiement?: string;
userId?: number;
sonumRole?: string;
}) {
const db = await getDb();
if (!db) return [];
const conditions = [];
// Visibilité : si pas gestionnaire, on ne montre que les fiches "tous"
if (filters.sonumRole !== "gestionnaire") {
conditions.push(eq(etablissements.visibilite, "tous"));
}
if (filters.region) conditions.push(eq(etablissements.region, filters.region));
if (filters.typeActivite) conditions.push(eq(etablissements.typeActivite, filters.typeActivite));
if (filters.tailleEffectifs) conditions.push(eq(etablissements.tailleEffectifs, filters.tailleEffectifs));
let query = db
.select({
id: etablissements.id,
finess: etablissements.finess,
nom: etablissements.nom,
region: etablissements.region,
departement: etablissements.departement,
typeActivite: etablissements.typeActivite,
tailleEffectifs: etablissements.tailleEffectifs,
referentId: etablissements.referentId,
visibilite: etablissements.visibilite,
accepteMiseEnRelation: etablissements.accepteMiseEnRelation,
})
.from(etablissements)
.where(conditions.length > 0 ? and(...conditions) : undefined)
.orderBy(etablissements.nom);
return query;
}
// ─── Logiciels par Établissement ─────────────────────────────────────────────
export async function getLogicielsByEtablissement(etablissementId: number) {
const db = await getDb();
if (!db) return [];
return db
.select({
id: logicielsEtablissements.id,
etablissementId: logicielsEtablissements.etablissementId,
solutionId: logicielsEtablissements.solutionId,
solutionNom: solutions.nom,
editeurNom: editeurs.nom,
blocFonctionnelNom: blocsFonctionnels.nom,
etatDeploiement: logicielsEtablissements.etatDeploiement,
modeHebergement: logicielsEtablissements.modeHebergement,
modeFacturation: logicielsEtablissements.modeFacturation,
interoperabilite: logicielsEtablissements.interoperabilite,
versionMajeure: logicielsEtablissements.versionMajeure,
commentaire: logicielsEtablissements.commentaire,
contactNom: logicielsEtablissements.contactNom,
contactFonction: logicielsEtablissements.contactFonction,
contactEmail: logicielsEtablissements.contactEmail,
createdAt: logicielsEtablissements.createdAt,
updatedAt: logicielsEtablissements.updatedAt,
})
.from(logicielsEtablissements)
.leftJoin(solutions, eq(logicielsEtablissements.solutionId, solutions.id))
.leftJoin(editeurs, eq(solutions.editeurId, editeurs.id))
.leftJoin(blocsFonctionnels, eq(solutions.blocFonctionnelId, blocsFonctionnels.id))
.where(eq(logicielsEtablissements.etablissementId, etablissementId))
.orderBy(logicielsEtablissements.createdAt);
}
export async function upsertLogicielEtablissement(data: {
id?: number;
etablissementId: number;
solutionId: number;
etatDeploiement: "demarrage" | "en_cours" | "operationnel" | "en_remplacement";
modeHebergement?: "hds" | "on_premise" | "hybride" | null;
modeFacturation?: "saas" | "achat_maintenance" | "location" | null;
interoperabilite?: "non" | "oui_interface" | "oui_eai" | null;
versionMajeure?: string | null;
commentaire?: string | null;
contactNom?: string | null;
contactFonction?: string | null;
contactEmail?: string | null;
saisiePar?: number;
}) {
const db = await getDb();
if (!db) return null;
if (data.id) {
await db.update(logicielsEtablissements).set({ ...data, updatedAt: new Date() }).where(eq(logicielsEtablissements.id, data.id));
return data.id;
}
const result = await db.insert(logicielsEtablissements).values(data);
return result[0];
}
export async function deleteLogicielEtablissement(id: number) {
const db = await getDb();
if (!db) return;
await db.delete(logicielsEtablissements).where(eq(logicielsEtablissements.id, id));
}
// ─── Traçabilité ──────────────────────────────────────────────────────────────
export async function recordConsultation(etablissementId: number, userId: number, userName: string) {
const db = await getDb();
if (!db) return;
await db.insert(consultations).values({ etablissementId, consultePar: userId, consultéParNom: userName });
}
export async function getConsultationCount(etablissementId: number) {
const db = await getDb();
if (!db) return 0;
const result = await db
.select({ count: sql<number>`count(*)` })
.from(consultations)
.where(eq(consultations.etablissementId, etablissementId));
return Number(result[0]?.count ?? 0);
}
export async function getConsultationsList(etablissementId: number) {
const db = await getDb();
if (!db) return [];
return db
.select()
.from(consultations)
.where(eq(consultations.etablissementId, etablissementId))
.orderBy(desc(consultations.createdAt))
.limit(50);
}
// ─── Demandes de Contact ──────────────────────────────────────────────────────
export async function createDemandeContact(data: {
etablissementCibleId: number;
demandeurId: number;
demandeurNom: string;
demandeurEmail: string;
message: string;
}) {
const db = await getDb();
if (!db) return null;
const result = await db.insert(demandesContact).values(data);
return result[0];
}
export async function getDemandesByDemandeur(demandeurId: number) {
const db = await getDb();
if (!db) return [];
return db
.select({
id: demandesContact.id,
etablissementNom: etablissements.nom,
message: demandesContact.message,
statut: demandesContact.statut,
reponse: demandesContact.reponse,
reponduAt: demandesContact.reponduAt,
createdAt: demandesContact.createdAt,
})
.from(demandesContact)
.leftJoin(etablissements, eq(demandesContact.etablissementCibleId, etablissements.id))
.where(eq(demandesContact.demandeurId, demandeurId))
.orderBy(desc(demandesContact.createdAt));
}
export async function getDemandesRecuesParEtablissement(referentId: number) {
const db = await getDb();
if (!db) return [];
return db
.select({
id: demandesContact.id,
etablissementNom: etablissements.nom,
demandeurNom: demandesContact.demandeurNom,
demandeurEmail: demandesContact.demandeurEmail,
message: demandesContact.message,
statut: demandesContact.statut,
reponse: demandesContact.reponse,
reponduAt: demandesContact.reponduAt,
createdAt: demandesContact.createdAt,
})
.from(demandesContact)
.leftJoin(etablissements, eq(demandesContact.etablissementCibleId, etablissements.id))
.where(eq(etablissements.referentId, referentId))
.orderBy(desc(demandesContact.createdAt));
}
export async function getAllDemandes() {
const db = await getDb();
if (!db) return [];
return db
.select({
id: demandesContact.id,
etablissementNom: etablissements.nom,
demandeurNom: demandesContact.demandeurNom,
demandeurEmail: demandesContact.demandeurEmail,
message: demandesContact.message,
statut: demandesContact.statut,
reponse: demandesContact.reponse,
reponduAt: demandesContact.reponduAt,
createdAt: demandesContact.createdAt,
})
.from(demandesContact)
.leftJoin(etablissements, eq(demandesContact.etablissementCibleId, etablissements.id))
.orderBy(desc(demandesContact.createdAt));
}
export async function repondreDemandeContact(id: number, reponse: string, reponsePar: number) {
const db = await getDb();
if (!db) return;
await db
.update(demandesContact)
.set({ reponse, reponsePar, statut: "repondu", reponduAt: new Date(), updatedAt: new Date() })
.where(eq(demandesContact.id, id));
}
export async function getDemandeById(id: number) {
const db = await getDb();
if (!db) return null;
const result = await db.select().from(demandesContact).where(eq(demandesContact.id, id)).limit(1);
return result[0] ?? null;
}