Checkpoint: Pagination serveur des établissements et des messages, tri serveur, navigation utilisateur, limitation de débit sur connexion locale, messages et demandes, avec 46 tests Vitest, typage TypeScript et build validés.
This commit is contained in:
100
server/db.ts
100
server/db.ts
@@ -1,4 +1,4 @@
|
||||
import { and, desc, eq, ilike, inArray, like, or, sql } from "drizzle-orm";
|
||||
import { and, asc, desc, eq, ilike, inArray, like, or, sql } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import {
|
||||
InsertUser,
|
||||
@@ -179,9 +179,13 @@ export async function searchEtablissements(filters: {
|
||||
etatDeploiement?: string;
|
||||
userId?: number;
|
||||
sonumRole?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
sortBy: "nom" | "region" | "typeActivite" | "tailleEffectifs";
|
||||
sortDirection: "asc" | "desc";
|
||||
}) {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
if (!db) return { items: [], total: 0, page: filters.page, pageSize: filters.pageSize };
|
||||
const conditions: any[] = [];
|
||||
|
||||
// Visibilité : si pas gestionnaire, on ne montre que les fiches "tous"
|
||||
@@ -193,7 +197,7 @@ export async function searchEtablissements(filters: {
|
||||
if (filters.sonumRole === "adherent" && filters.userId) {
|
||||
const affectations = await getAffectationsByUser(filters.userId);
|
||||
if (affectations.length === 0) {
|
||||
return []; // Aucun établissement affecté
|
||||
return { items: [], total: 0, page: filters.page, pageSize: filters.pageSize };
|
||||
}
|
||||
conditions.push(inArray(etablissements.id, affectations));
|
||||
}
|
||||
@@ -230,23 +234,44 @@ export async function searchEtablissements(filters: {
|
||||
conditions.push(inArray(etablissements.id, subquery));
|
||||
}
|
||||
|
||||
const result = await 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 result;
|
||||
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
||||
const sortColumn = {
|
||||
nom: etablissements.nom,
|
||||
region: etablissements.region,
|
||||
typeActivite: etablissements.typeActivite,
|
||||
tailleEffectifs: etablissements.tailleEffectifs,
|
||||
}[filters.sortBy];
|
||||
const orderBy = filters.sortDirection === "desc" ? desc(sortColumn) : asc(sortColumn);
|
||||
|
||||
// Les requêtes sont indépendantes : total global et page courante partent en parallèle.
|
||||
const [items, totalRows] = await Promise.all([
|
||||
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(whereClause)
|
||||
.orderBy(orderBy, asc(etablissements.id))
|
||||
.limit(filters.pageSize)
|
||||
.offset((filters.page - 1) * filters.pageSize),
|
||||
db.select({ total: sql<number>`COUNT(*)` }).from(etablissements).where(whereClause),
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
total: Number(totalRows[0]?.total ?? 0),
|
||||
page: filters.page,
|
||||
pageSize: filters.pageSize,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Logiciels par Établissement ─────────────────────────────────────────────
|
||||
@@ -1137,14 +1162,35 @@ export async function getCanauxForUser(userId: number, isGestionnaire: boolean)
|
||||
/**
|
||||
* Récupère les messages d'un canal.
|
||||
*/
|
||||
export async function getMessagesCanal(canalId: number) {
|
||||
export async function getMessagesCanal(canalId: number, page: number, pageSize: number) {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
return db
|
||||
.select()
|
||||
.from(messagesCanaux)
|
||||
.where(eq(messagesCanaux.canalId, canalId))
|
||||
.orderBy(messagesCanaux.createdAt);
|
||||
if (!db) return { items: [], total: 0, page, pageSize, hasMore: false };
|
||||
|
||||
/**
|
||||
* La base lit les messages les plus récents en premier pour rendre le premier
|
||||
* affichage rapide, puis la page est renversée avant l'affichage chronologique.
|
||||
*/
|
||||
const [newestFirst, totalRows] = await Promise.all([
|
||||
db
|
||||
.select()
|
||||
.from(messagesCanaux)
|
||||
.where(eq(messagesCanaux.canalId, canalId))
|
||||
.orderBy(desc(messagesCanaux.createdAt), desc(messagesCanaux.id))
|
||||
.limit(pageSize)
|
||||
.offset((page - 1) * pageSize),
|
||||
db
|
||||
.select({ total: sql<number>`COUNT(*)` })
|
||||
.from(messagesCanaux)
|
||||
.where(eq(messagesCanaux.canalId, canalId)),
|
||||
]);
|
||||
const total = Number(totalRows[0]?.total ?? 0);
|
||||
return {
|
||||
items: newestFirst.reverse(),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
hasMore: page * pageSize < total,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user