import { Router, Response } from "express"; import { db } from "@workspace/db"; import { clientsTable, weeklyReportsTable } from "@workspace/db"; import { eq } from "drizzle-orm"; import { requireAuth, AuthRequest } from "../middleware/auth.js"; import { User, isAdmin } from "../lib/auth-utils.js"; import { logAudit } from "../lib/audit-logger.js"; import multer from "multer"; import { createClient } from "@supabase/supabase-js"; import crypto from "crypto"; const router = Router(); // Setup Supabase const supabaseUrl = process.env.SUPABASE_URL!; const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_KEY!; const supabase = createClient(supabaseUrl, supabaseKey); // Multer config for PDF only, max 10MB const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024, // 10MB }, fileFilter: (_req, file, cb) => { if (file.mimetype === "application/pdf") { cb(null, true); } else { cb(new Error("Only PDF files are allowed")); } }, }); // GET /clients/:id/weekly-reports router.get("/clients/:id/weekly-reports", requireAuth, async (req: AuthRequest, res: Response): Promise => { try { const clientId = Number(req.params.id); const [client] = await db.select().from(clientsTable).where(eq(clientsTable.id, clientId)); if (!client) { res.status(404).json({ error: "العميل غير موجود" }); return; } const reports = await db .select() .from(weeklyReportsTable) .where(eq(weeklyReportsTable.clientId, clientId)) .orderBy(weeklyReportsTable.createdAt); res.json(reports.map(report => ({ ...report, createdAt: report.createdAt.toISOString() }))); } catch (err: any) { console.error("Error fetching weekly reports:", err); res.status(500).json({ error: "حدث خطأ أثناء جلب التقارير الأسبوعية" }); } }); // POST /clients/:id/weekly-reports router.post("/clients/:id/weekly-reports", requireAuth, upload.single("file"), async (req: AuthRequest, res: Response): Promise => { try { const clientId = Number(req.params.id); const user = req.user as User & { supabaseUid?: string }; const file = req.file; if (!file) { res.status(400).json({ error: "الرجاء إرفاق ملف PDF" }); return; } const [client] = await db.select().from(clientsTable).where(eq(clientsTable.id, clientId)); if (!client) { res.status(404).json({ error: "العميل غير موجود" }); return; } // Upload to Supabase Storage const fileExt = file.originalname.split(".").pop(); const uniqueId = crypto.randomUUID(); const storagePath = `clients/${clientId}/${uniqueId}.${fileExt}`; const { error: uploadError } = await supabase.storage .from("weekly-reports") .upload(storagePath, file.buffer, { contentType: file.mimetype, upsert: false, }); if (uploadError) { console.error("Supabase upload error:", uploadError); res.status(500).json({ error: "فشل رفع الملف إلى التخزين السحابي" }); return; } const [report] = await db.insert(weeklyReportsTable).values({ clientId, fileName: file.originalname, fileUrl: storagePath, fileSize: file.size, mimeType: file.mimetype, uploadedByUserId: user.id, }).returning(); // Audit log await logAudit({ userId: String(user.id), action: "weekly_report_uploaded", entityType: "client", entityId: clientId, details: { reportId: report.id, fileName: report.fileName, supabaseUid: user.supabaseUid ?? null, }, req, }); res.status(201).json({ ...report, createdAt: report.createdAt.toISOString(), }); } catch (err: any) { console.error("Error uploading weekly report:", err); res.status(500).json({ error: err.message || "حدث خطأ أثناء رفع التقرير" }); } }); // DELETE /clients/:id/weekly-reports/:reportId router.delete("/clients/:id/weekly-reports/:reportId", requireAuth, async (req: AuthRequest, res: Response): Promise => { try { const clientId = Number(req.params.id); const reportId = Number(req.params.reportId); const user = req.user as User & { supabaseUid?: string }; const [report] = await db.select().from(weeklyReportsTable).where(eq(weeklyReportsTable.id, reportId)); if (!report) { res.status(404).json({ error: "التقرير غير موجود" }); return; } if (report.clientId !== clientId) { res.status(400).json({ error: "التقرير لا ينتمي لهذا العميل" }); return; } const isUploader = report.uploadedByUserId === user.id; const isUserAdmin = isAdmin(user); if (!isUploader && !isUserAdmin) { res.status(403).json({ error: "غير مصرح لك بحذف هذا التقرير" }); return; } // Delete from Supabase Storage try { await supabase.storage.from("weekly-reports").remove([report.fileUrl]); } catch (storageErr) { console.warn("Storage deletion warning (continuing DB deletion):", storageErr); } // Delete from DB await db.delete(weeklyReportsTable).where(eq(weeklyReportsTable.id, reportId)); // Audit log await logAudit({ userId: String(user.id), action: "weekly_report_deleted", entityType: "client", entityId: clientId, details: { reportId, fileName: report.fileName, supabaseUid: user.supabaseUid ?? null, }, req, }); res.json({ success: true }); } catch (err: any) { console.error("Error deleting weekly report:", err); res.status(500).json({ error: "حدث خطأ أثناء حذف التقرير" }); } }); // GET /clients/:id/weekly-reports/:reportId/download — returns a signed URL (1 hour) router.get("/clients/:id/weekly-reports/:reportId/download", requireAuth, async (req: AuthRequest, res: Response): Promise => { try { const reportId = Number(req.params.reportId); const [report] = await db.select().from(weeklyReportsTable).where(eq(weeklyReportsTable.id, reportId)); if (!report) { res.status(404).json({ error: "التقرير غير موجود" }); return; } const { data, error } = await supabase.storage .from("weekly-reports") .createSignedUrl(report.fileUrl, 3600); // 1 hour if (error || !data) { console.error("Signed URL generation error:", error); res.status(500).json({ error: "فشل إنشاء رابط التحميل" }); return; } res.json({ signedUrl: data.signedUrl }); } catch (err: any) { console.error("Error generating signed URL:", err); res.status(500).json({ error: "حدث خطأ أثناء تحميل التقرير" }); } }); export default router;