o134 commited on
Commit
e33a019
·
verified ·
1 Parent(s): ac0b751

Upload api-server\src\routes\weekly-reports.ts with huggingface_hub

Browse files
api-server//src//routes//weekly-reports.ts ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Router, Response } from "express";
2
+ import { db } from "@workspace/db";
3
+ import { clientsTable, weeklyReportsTable } from "@workspace/db";
4
+ import { eq } from "drizzle-orm";
5
+ import { requireAuth, AuthRequest } from "../middleware/auth.js";
6
+ import { User, isAdmin } from "../lib/auth-utils.js";
7
+ import { logAudit } from "../lib/audit-logger.js";
8
+ import multer from "multer";
9
+ import { createClient } from "@supabase/supabase-js";
10
+ import crypto from "crypto";
11
+
12
+ const router = Router();
13
+
14
+ // Setup Supabase
15
+ const supabaseUrl = process.env.SUPABASE_URL!;
16
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_KEY!;
17
+ const supabase = createClient(supabaseUrl, supabaseKey);
18
+
19
+ // Multer config for PDF only, max 10MB
20
+ const upload = multer({
21
+ storage: multer.memoryStorage(),
22
+ limits: {
23
+ fileSize: 10 * 1024 * 1024, // 10MB
24
+ },
25
+ fileFilter: (_req, file, cb) => {
26
+ if (file.mimetype === "application/pdf") {
27
+ cb(null, true);
28
+ } else {
29
+ cb(new Error("Only PDF files are allowed"));
30
+ }
31
+ },
32
+ });
33
+
34
+ // GET /clients/:id/weekly-reports
35
+ router.get("/clients/:id/weekly-reports", requireAuth, async (req: AuthRequest, res: Response): Promise<void> => {
36
+ try {
37
+ const clientId = Number(req.params.id);
38
+
39
+ const [client] = await db.select().from(clientsTable).where(eq(clientsTable.id, clientId));
40
+ if (!client) {
41
+ res.status(404).json({ error: "العميل غير موجود" });
42
+ return;
43
+ }
44
+
45
+ const reports = await db
46
+ .select()
47
+ .from(weeklyReportsTable)
48
+ .where(eq(weeklyReportsTable.clientId, clientId))
49
+ .orderBy(weeklyReportsTable.createdAt);
50
+
51
+ res.json(reports.map(report => ({
52
+ ...report,
53
+ createdAt: report.createdAt.toISOString()
54
+ })));
55
+ } catch (err: any) {
56
+ console.error("Error fetching weekly reports:", err);
57
+ res.status(500).json({ error: "حدث خطأ أثناء جلب التقارير الأسبوعية" });
58
+ }
59
+ });
60
+
61
+ // POST /clients/:id/weekly-reports
62
+ router.post("/clients/:id/weekly-reports", requireAuth, upload.single("file"), async (req: AuthRequest, res: Response): Promise<void> => {
63
+ try {
64
+ const clientId = Number(req.params.id);
65
+ const user = req.user as User & { supabaseUid?: string };
66
+ const file = req.file;
67
+
68
+ if (!file) {
69
+ res.status(400).json({ error: "الرجاء إرفاق ملف PDF" });
70
+ return;
71
+ }
72
+
73
+ const [client] = await db.select().from(clientsTable).where(eq(clientsTable.id, clientId));
74
+ if (!client) {
75
+ res.status(404).json({ error: "العميل غير موجود" });
76
+ return;
77
+ }
78
+
79
+ // Upload to Supabase Storage
80
+ const fileExt = file.originalname.split(".").pop();
81
+ const uniqueId = crypto.randomUUID();
82
+ const storagePath = `clients/${clientId}/${uniqueId}.${fileExt}`;
83
+
84
+ const { error: uploadError } = await supabase.storage
85
+ .from("weekly-reports")
86
+ .upload(storagePath, file.buffer, {
87
+ contentType: file.mimetype,
88
+ upsert: false,
89
+ });
90
+
91
+ if (uploadError) {
92
+ console.error("Supabase upload error:", uploadError);
93
+ res.status(500).json({ error: "فشل رفع الملف إلى التخزين السحابي" });
94
+ return;
95
+ }
96
+
97
+ const [report] = await db.insert(weeklyReportsTable).values({
98
+ clientId,
99
+ fileName: file.originalname,
100
+ fileUrl: storagePath,
101
+ fileSize: file.size,
102
+ mimeType: file.mimetype,
103
+ uploadedByUserId: user.id,
104
+ }).returning();
105
+
106
+ // Audit log
107
+ await logAudit({
108
+ userId: String(user.id),
109
+ action: "weekly_report_uploaded",
110
+ entityType: "client",
111
+ entityId: clientId,
112
+ details: {
113
+ reportId: report.id,
114
+ fileName: report.fileName,
115
+ supabaseUid: user.supabaseUid ?? null,
116
+ },
117
+ req,
118
+ });
119
+
120
+ res.status(201).json({
121
+ ...report,
122
+ createdAt: report.createdAt.toISOString(),
123
+ });
124
+ } catch (err: any) {
125
+ console.error("Error uploading weekly report:", err);
126
+ res.status(500).json({ error: err.message || "حدث خطأ أثناء رفع التقرير" });
127
+ }
128
+ });
129
+
130
+ // DELETE /clients/:id/weekly-reports/:reportId
131
+ router.delete("/clients/:id/weekly-reports/:reportId", requireAuth, async (req: AuthRequest, res: Response): Promise<void> => {
132
+ try {
133
+ const clientId = Number(req.params.id);
134
+ const reportId = Number(req.params.reportId);
135
+ const user = req.user as User & { supabaseUid?: string };
136
+
137
+ const [report] = await db.select().from(weeklyReportsTable).where(eq(weeklyReportsTable.id, reportId));
138
+ if (!report) {
139
+ res.status(404).json({ error: "التقرير غير موجود" });
140
+ return;
141
+ }
142
+
143
+ if (report.clientId !== clientId) {
144
+ res.status(400).json({ error: "التقرير لا ينتمي لهذا العميل" });
145
+ return;
146
+ }
147
+
148
+ const isUploader = report.uploadedByUserId === user.id;
149
+ const isUserAdmin = isAdmin(user);
150
+
151
+ if (!isUploader && !isUserAdmin) {
152
+ res.status(403).json({ error: "غير مصرح لك بحذف هذا التقرير" });
153
+ return;
154
+ }
155
+
156
+ // Delete from Supabase Storage
157
+ try {
158
+ await supabase.storage.from("weekly-reports").remove([report.fileUrl]);
159
+ } catch (storageErr) {
160
+ console.warn("Storage deletion warning (continuing DB deletion):", storageErr);
161
+ }
162
+
163
+ // Delete from DB
164
+ await db.delete(weeklyReportsTable).where(eq(weeklyReportsTable.id, reportId));
165
+
166
+ // Audit log
167
+ await logAudit({
168
+ userId: String(user.id),
169
+ action: "weekly_report_deleted",
170
+ entityType: "client",
171
+ entityId: clientId,
172
+ details: {
173
+ reportId,
174
+ fileName: report.fileName,
175
+ supabaseUid: user.supabaseUid ?? null,
176
+ },
177
+ req,
178
+ });
179
+
180
+ res.json({ success: true });
181
+ } catch (err: any) {
182
+ console.error("Error deleting weekly report:", err);
183
+ res.status(500).json({ error: "حدث خطأ أثناء حذف التقرير" });
184
+ }
185
+ });
186
+
187
+ // GET /clients/:id/weekly-reports/:reportId/download — returns a signed URL (1 hour)
188
+ router.get("/clients/:id/weekly-reports/:reportId/download", requireAuth, async (req: AuthRequest, res: Response): Promise<void> => {
189
+ try {
190
+ const reportId = Number(req.params.reportId);
191
+ const [report] = await db.select().from(weeklyReportsTable).where(eq(weeklyReportsTable.id, reportId));
192
+ if (!report) {
193
+ res.status(404).json({ error: "التقرير غير موجود" });
194
+ return;
195
+ }
196
+
197
+ const { data, error } = await supabase.storage
198
+ .from("weekly-reports")
199
+ .createSignedUrl(report.fileUrl, 3600); // 1 hour
200
+
201
+ if (error || !data) {
202
+ console.error("Signed URL generation error:", error);
203
+ res.status(500).json({ error: "فشل إنشاء رابط التحميل" });
204
+ return;
205
+ }
206
+
207
+ res.json({ signedUrl: data.signedUrl });
208
+ } catch (err: any) {
209
+ console.error("Error generating signed URL:", err);
210
+ res.status(500).json({ error: "حدث خطأ أثناء تحميل التقرير" });
211
+ }
212
+ });
213
+
214
+ export default router;