mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-19 05:06:56 +02:00
Une extraction Qlik produit plusieurs milliers de lignes de diagnostic. Un terminal les tronque et n'en laisse que la fin, alors que l'information decisive — carte du modele Qlik, champs date retenus, choix des periodes — se trouve en TETE. Diagnostiquer sur la fin du log revient a travailler a l'aveugle. - src/lib/log-capture.ts : instrumente console.log/warn/error une fois, recopie chaque ligne horodatee dans les captures ouvertes, ecrit au fil de l'eau dans le repertoire temporaire (un job qui fait tomber le process laisse quand meme son journal). Bornes : 200 000 lignes, 8 000 caracteres par ligne, 20 fichiers conserves. - GET /api/logs : liste JSON ; GET /api/logs?id=<jobId> : le fichier complet en piece jointe. Admin uniquement. - La sync Qlik ouvre la capture AVANT son premier log et la ferme dans un finally, avec le statut final — les deux modes (fournisseur et produit). - Parametres : section « Journaux serveur », un bouton Telecharger par job, le plus recent en premier, badge « en cours » pour un job vivant. Aucun secret ne transite dans ces journaux : l'extracteur ne journalise que le NOM du cookie de session, jamais sa valeur ni le mot de passe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
55 lines
2.0 KiB
TypeScript
55 lines
2.0 KiB
TypeScript
/**
|
|
* CollectFlow — Journaux serveur des jobs (liste + téléchargement).
|
|
*
|
|
* `GET /api/logs` → liste des journaux disponibles (JSON)
|
|
* `GET /api/logs?id=<jobId>` → le journal complet en pièce jointe (text/plain)
|
|
*
|
|
* Réservé aux admins : un journal d'extraction contient les diagnostics internes
|
|
* du modèle Qlik et les codes articles manipulés. Aucun secret n'y transite —
|
|
* l'extracteur ne journalise que le NOM du cookie de session, jamais sa valeur,
|
|
* et jamais le mot de passe Qlik.
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { auth } from "@/lib/auth";
|
|
import { listCaptures, readCapture } from "@/lib/log-capture";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
|
|
async function requireAdmin(): Promise<NextResponse | null> {
|
|
const session = await auth();
|
|
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
if ((session.user as { role?: string } | undefined)?.role !== "admin") {
|
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const refus = await requireAdmin();
|
|
if (refus) return refus;
|
|
|
|
const id = req.nextUrl.searchParams.get("id");
|
|
if (!id) {
|
|
return NextResponse.json({ success: true, logs: await listCaptures() });
|
|
}
|
|
|
|
const contenu = await readCapture(id);
|
|
if (contenu == null) {
|
|
return NextResponse.json({ error: "Journal introuvable ou expiré" }, { status: 404 });
|
|
}
|
|
|
|
// Nom de fichier assaini : l'id vient d'un job interne, mais il finit dans
|
|
// un en-tête HTTP.
|
|
const nom = `${id.replace(/[^A-Za-z0-9_.-]/g, "_")}.log`;
|
|
return new NextResponse(contenu, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "text/plain; charset=utf-8",
|
|
"Content-Disposition": `attachment; filename="${nom}"`,
|
|
"Cache-Control": "no-store",
|
|
},
|
|
});
|
|
}
|