Checkpoint: Implémentation des fonctionnalités de communication : formulaire de contact avec choix email/canal, canaux de discussion intégrés (page Mes Échanges), demandes de mise en relation avec gestion admin des canaux, navigation mise à jour dans DashboardLayout, bouton Demande de mise en relation sur fiche établissement. Migration DB : tables canaux_discussion, membres_canal, messages_canal, demandes_mise_en_relation.

This commit is contained in:
Manus
2026-05-13 10:40:13 -04:00
parent 46321a1eaf
commit 0205859880
13 changed files with 2437 additions and 57 deletions

View File

@@ -3,12 +3,17 @@ import { drizzle } from "drizzle-orm/mysql2";
import {
InsertUser,
blocsFonctionnels,
canauxDiscussion,
consultations,
demandesContact,
demandesMiseEnRelation,
editeurs,
etablissements,
logicielsEtablissements,
membresCanauxDiscussion,
messagesCanaux,
solutions,
userEtablissements,
users,
} from "../drizzle/schema";
import { ENV } from "./_core/env";
@@ -430,7 +435,7 @@ export async function getDemandeById(id: number) {
// ─── Auth locale ──────────────────────────────────────────────────────────────
import { localCredentials, userEtablissements } from "../drizzle/schema";
import { localCredentials } from "../drizzle/schema";
import bcrypt from "bcryptjs";
import { nanoid } from "nanoid";
@@ -1031,3 +1036,222 @@ export async function setAdherentsForEtablissement(etablissementId: number, user
);
}
}
// ─── Canaux de Discussion ─────────────────────────────────────────────────────
/**
* Crée un canal de discussion et ajoute les membres initiaux.
*/
export async function createCanal(data: {
titre: string;
description?: string;
type: "contact_referent" | "mise_en_relation_public" | "mise_en_relation_prive";
visibilite: "public" | "prive";
etablissementId?: number;
demandeContactId?: number;
demandeMiseEnRelationId?: number;
creePar: number;
membreIds: number[];
}) {
const db = await getDb();
if (!db) throw new Error("DB unavailable");
const [result] = await db.insert(canauxDiscussion).values({
titre: data.titre,
description: data.description,
type: data.type,
visibilite: data.visibilite,
etablissementId: data.etablissementId,
demandeContactId: data.demandeContactId,
demandeMiseEnRelationId: data.demandeMiseEnRelationId,
creePar: data.creePar,
});
const canalId = (result as any).insertId as number;
if (data.membreIds.length > 0) {
await db.insert(membresCanauxDiscussion).values(
data.membreIds.map((uid) => ({ canalId, userId: uid, role: "membre" as const }))
);
}
return canalId;
}
/**
* Récupère les canaux accessibles à un utilisateur (membre ou gestionnaire).
*/
export async function getCanauxForUser(userId: number, isGestionnaire: boolean) {
const db = await getDb();
if (!db) return [];
if (isGestionnaire) {
return db.select().from(canauxDiscussion).orderBy(desc(canauxDiscussion.updatedAt));
}
// Canaux dont l'utilisateur est membre
const memberships = await db
.select({ canalId: membresCanauxDiscussion.canalId })
.from(membresCanauxDiscussion)
.where(eq(membresCanauxDiscussion.userId, userId));
const canalIds = memberships.map((m) => m.canalId);
if (canalIds.length === 0) return [];
return db
.select()
.from(canauxDiscussion)
.where(inArray(canauxDiscussion.id, canalIds))
.orderBy(desc(canauxDiscussion.updatedAt));
}
/**
* Récupère les messages d'un canal.
*/
export async function getMessagesCanal(canalId: number) {
const db = await getDb();
if (!db) return [];
return db
.select()
.from(messagesCanaux)
.where(eq(messagesCanaux.canalId, canalId))
.orderBy(messagesCanaux.createdAt);
}
/**
* Envoie un message dans un canal.
*/
export async function sendMessageCanal(data: {
canalId: number;
auteurId: number;
auteurNom: string;
contenu: string;
}) {
const db = await getDb();
if (!db) throw new Error("DB unavailable");
await db.insert(messagesCanaux).values(data);
// Mettre à jour updatedAt du canal
await db
.update(canauxDiscussion)
.set({ updatedAt: new Date() })
.where(eq(canauxDiscussion.id, data.canalId));
}
/**
* Vérifie si un utilisateur est membre d'un canal.
*/
export async function isMemberOfCanal(canalId: number, userId: number): Promise<boolean> {
const db = await getDb();
if (!db) return false;
const rows = await db
.select()
.from(membresCanauxDiscussion)
.where(and(eq(membresCanauxDiscussion.canalId, canalId), eq(membresCanauxDiscussion.userId, userId)));
return rows.length > 0;
}
/**
* Récupère un canal par son ID.
*/
export async function getCanalById(id: number) {
const db = await getDb();
if (!db) return null;
const rows = await db.select().from(canauxDiscussion).where(eq(canauxDiscussion.id, id));
return rows[0] ?? null;
}
/**
* Récupère les membres d'un canal.
*/
export async function getMembresCanal(canalId: number) {
const db = await getDb();
if (!db) return [];
return db
.select({
id: users.id,
name: users.name,
email: users.email,
sonumRole: users.sonumRole,
role: membresCanauxDiscussion.role,
})
.from(membresCanauxDiscussion)
.innerJoin(users, eq(membresCanauxDiscussion.userId, users.id))
.where(eq(membresCanauxDiscussion.canalId, canalId));
}
/**
* Ajoute des membres à un canal existant.
*/
export async function addMembresCanal(canalId: number, userIds: number[]) {
const db = await getDb();
if (!db) return;
const existing = await db
.select({ userId: membresCanauxDiscussion.userId })
.from(membresCanauxDiscussion)
.where(eq(membresCanauxDiscussion.canalId, canalId));
const existingIds = new Set(existing.map((m) => m.userId));
const toAdd = userIds.filter((id) => !existingIds.has(id));
if (toAdd.length > 0) {
await db.insert(membresCanauxDiscussion).values(
toAdd.map((uid) => ({ canalId, userId: uid, role: "membre" as const }))
);
}
}
// ─── Demandes de Mise en Relation ─────────────────────────────────────────────
/**
* Crée une demande de mise en relation.
*/
export async function createDemandeMiseEnRelation(data: {
demandeurId: number;
demandeurNom: string;
demandeurEmail?: string;
etablissementDemandeurId?: number;
sujet: string;
message: string;
}) {
const db = await getDb();
if (!db) throw new Error("DB unavailable");
const [result] = await db.insert(demandesMiseEnRelation).values(data);
return (result as any).insertId as number;
}
/**
* Récupère toutes les demandes de mise en relation (pour les gestionnaires).
*/
export async function getAllDemandesMiseEnRelation() {
const db = await getDb();
if (!db) return [];
return db
.select()
.from(demandesMiseEnRelation)
.orderBy(desc(demandesMiseEnRelation.createdAt));
}
/**
* Récupère les demandes de mise en relation d'un utilisateur.
*/
export async function getDemandesMiseEnRelationByUser(userId: number) {
const db = await getDb();
if (!db) return [];
return db
.select()
.from(demandesMiseEnRelation)
.where(eq(demandesMiseEnRelation.demandeurId, userId))
.orderBy(desc(demandesMiseEnRelation.createdAt));
}
/**
* Marque une demande de mise en relation comme traitée et lui associe un canal.
*/
export async function traiterDemandeMiseEnRelation(id: number, traitePar: number, canalId?: number) {
const db = await getDb();
if (!db) return;
await db
.update(demandesMiseEnRelation)
.set({ statut: "traite", traitePar, traiteAt: new Date(), canalId: canalId ?? null })
.where(eq(demandesMiseEnRelation.id, id));
}
/**
* Récupère une demande de mise en relation par son ID.
*/
export async function getDemandeMiseEnRelationById(id: number) {
const db = await getDb();
if (!db) return null;
const rows = await db.select().from(demandesMiseEnRelation).where(eq(demandesMiseEnRelation.id, id));
return rows[0] ?? null;
}