Spaces:
Sleeping
Sleeping
| 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<void> => { | |
| 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<void> => { | |
| 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<void> => { | |
| 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<void> => { | |
| 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; | |