operation-cycle / api-server /src /routes /weekly-reports.ts
o134's picture
Upload full TeamTasker system with all fixes
8314cf4 verified
Raw
History Blame Contribute Delete
6.99 kB
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;