sare26 commited on
Commit
d4423f5
·
verified ·
1 Parent(s): 01abfc5

Update artifacts/api-server/src/routes/admin/index.ts

Browse files
artifacts/api-server/src/routes/admin/index.ts CHANGED
@@ -1,34 +1,40 @@
1
  import { Router, type Request, type Response } from "express";
2
- import { getAuth, clerkClient } from "@clerk/express";
3
  import { db, usersTable, subscriptionsTable, appConfigTable, blockedEmailsTable } from "@workspace/db";
4
- import type { InferSelectModel } from "drizzle-orm";
5
  import { eq } from "drizzle-orm";
6
  import { broadcastToUsers } from "../../lib/sseManager";
7
 
8
  const router = Router();
9
 
 
 
 
 
 
10
  const OWNER_EMAIL = "saresare2555@hotmail.com";
11
 
12
  async function requireOwner(req: Request, res: Response): Promise<string | null> {
13
- const auth = getAuth(req);
14
- const userId: string | null = auth?.userId ?? null;
15
- if (!userId) {
16
  res.status(401).json({ error: "غير مصرح" });
17
  return null;
18
  }
19
- let email: string | null = null;
20
- try {
21
- const user = await clerkClient.users.getUser(userId);
22
- email = user.primaryEmailAddress?.emailAddress ?? null;
23
- } catch {
24
  res.status(401).json({ error: "فشل التحقق من الهوية" });
25
  return null;
26
  }
27
- if (email?.toLowerCase() !== OWNER_EMAIL.toLowerCase()) {
 
 
28
  res.status(403).json({ error: "ممنوع — للمالك فقط" });
29
  return null;
30
  }
31
- return userId;
 
32
  }
33
 
34
  router.get("/users", async (req: Request, res: Response) => {
@@ -36,26 +42,40 @@ router.get("/users", async (req: Request, res: Response) => {
36
  if (!owner) return;
37
 
38
  try {
39
- const clerkUsers = await clerkClient.users.getUserList({ limit: 200 });
40
- const dbUsers = await db.select({ id: usersTable.id, email: usersTable.email, ipAddress: usersTable.ipAddress, lastSeenAt: usersTable.lastSeenAt, isBanned: usersTable.isBanned, bannedAt: usersTable.bannedAt }).from(usersTable);
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  const dbSubs = await db.select().from(subscriptionsTable);
42
 
43
  const dbMap = new Map(dbUsers.map((u) => [u.id, u]));
44
  const subMap = new Map(dbSubs.map((s) => [s.userId, s]));
45
 
46
- const users = clerkUsers.data.map((cu) => {
47
- const email = cu.primaryEmailAddress?.emailAddress ?? null;
48
- const sub = subMap.get(cu.id);
49
- const dbUser = dbMap.get(cu.id);
 
50
  return {
51
- id: cu.id,
52
  email,
53
  ipAddress: dbUser?.ipAddress ?? null,
54
  lastSeenAt: dbUser?.lastSeenAt ? dbUser.lastSeenAt.toISOString() : null,
55
  isBanned: dbUser?.isBanned ?? false,
56
  bannedAt: dbUser?.bannedAt ? dbUser.bannedAt.toISOString() : null,
57
- createdAt: new Date(cu.createdAt).toISOString(),
58
- lastSignIn: cu.lastSignInAt ? new Date(cu.lastSignInAt).toISOString() : null,
59
  subscription: sub
60
  ? {
61
  status: sub.status,
@@ -63,7 +83,7 @@ router.get("/users", async (req: Request, res: Response) => {
63
  currentPeriodEnd: sub.currentPeriodEnd?.toISOString() ?? null,
64
  }
65
  : null,
66
- inDb: dbMap.has(cu.id),
67
  };
68
  });
69
 
@@ -85,28 +105,21 @@ router.patch("/subscription/:userId", async (req: Request, res: Response) => {
85
  };
86
 
87
  try {
88
- // 1) تأكد من وجود المستخدم في جدول users (ينشئه إذا لم يكن موجوداً)
89
  await db
90
  .insert(usersTable)
91
  .values({ id: userId, isBanned: false })
92
- .onConflictDoUpdate({
93
- target: usersTable.id,
94
- set: { id: userId },
95
- });
96
 
97
- // 2) تحضير قيم التحديث
98
  const updates: Record<string, unknown> = { updatedAt: new Date() };
99
  if (status !== undefined) updates.status = status;
100
  if (trialEnd !== undefined) updates.trialEnd = trialEnd ? new Date(trialEnd) : null;
101
  if (currentPeriodEnd !== undefined) updates.currentPeriodEnd = currentPeriodEnd ? new Date(currentPeriodEnd) : null;
102
 
103
- // 3) جرب التحديث أولاً
104
  const result = await db
105
  .update(subscriptionsTable)
106
  .set(updates)
107
  .where(eq(subscriptionsTable.userId, userId));
108
 
109
- // 4) إذا لم يوجد سجل اشتراك، أنشئ واحداً جديداً
110
  if (result.rowCount === 0) {
111
  await db.insert(subscriptionsTable).values({
112
  userId,
@@ -127,11 +140,10 @@ router.delete("/paypal-config", async (req: Request, res: Response) => {
127
  const owner = await requireOwner(req, res);
128
  if (!owner) return;
129
  try {
130
- const { appConfigTable: cfg } = await import("@workspace/db");
131
- await db.delete(cfg).where(eq(cfg.key, "paypal_product_id"));
132
- await db.delete(cfg).where(eq(cfg.key, "paypal_plan_id_v2"));
133
- await db.delete(cfg).where(eq(cfg.key, "paypal_plan_id"));
134
- res.json({ success: true, message: "تم مسح إعدادات PayPal — ستُعاد إنشاؤها عند أول اشتراك" });
135
  } catch (err: any) {
136
  res.status(500).json({ error: err.message });
137
  }
@@ -167,23 +179,18 @@ router.patch("/extension-update", async (req: Request, res: Response) => {
167
  }
168
  });
169
 
170
- // ✅ حذف مستخدم: ban بدل delete - يمكن فك الحظر لاحقاً
171
  router.delete("/users/:id", async (req: Request, res: Response) => {
172
  const owner = await requireOwner(req, res);
173
  if (!owner) return;
174
-
175
  const targetId = req.params.id;
176
  if (!targetId) {
177
  res.status(400).json({ error: "معرف المستخدم مطلوب" });
178
  return;
179
  }
180
-
181
  try {
182
  await db.delete(subscriptionsTable).where(eq(subscriptionsTable.userId, targetId));
183
  await db.delete(usersTable).where(eq(usersTable.id, targetId));
184
- // ban بدل delete - يمكن فك الحظر لاحقاً
185
- try { await clerkClient.users.banUser(targetId); } catch { /* ignore */ }
186
- res.json({ success: true, message: "تم حظر المستخدم - يمكن فك الحظر لاحقاً" });
187
  } catch (err: any) {
188
  res.status(500).json({ error: err.message });
189
  }
@@ -200,7 +207,6 @@ router.get("/blocked-emails", async (req: Request, res: Response) => {
200
  }
201
  });
202
 
203
- // ✅ حظر بريد: ban بدل delete - مع حذف الاشتراك فقط
204
  router.post("/blocked-emails", async (req: Request, res: Response) => {
205
  const owner = await requireOwner(req, res);
206
  if (!owner) return;
@@ -211,64 +217,27 @@ router.post("/blocked-emails", async (req: Request, res: Response) => {
211
  }
212
  try {
213
  const normalized = email.toLowerCase().trim();
214
- const isEmail = normalized.includes("@");
215
-
216
  await db.insert(blockedEmailsTable)
217
  .values({ email: normalized, reason: reason ?? null })
218
- .onConflictDoUpdate({
219
- target: blockedEmailsTable.email,
220
- set: { reason: reason ?? null },
221
- });
222
-
223
- if (isEmail) {
224
- const clerkUsers = await clerkClient.users.getUserList({ emailAddress: [normalized], limit: 5 });
225
- for (const u of clerkUsers.data) {
226
- try {
227
- // ✅ ban بدل delete - يمكن فك الحظر لاحقاً
228
- await clerkClient.users.banUser(u.id);
229
- // حذف الاشتراك فقط
230
- await db.delete(subscriptionsTable).where(eq(subscriptionsTable.userId, u.id));
231
- } catch { /* ignore */ }
232
- }
233
- } else {
234
- const dbUsers = await db.select({ id: usersTable.id }).from(usersTable)
235
- .where(eq(usersTable.ipAddress, normalized));
236
- for (const u of dbUsers) {
237
- try { await clerkClient.users.banUser(u.id); } catch { /* ignore */ }
238
- }
239
- }
240
-
241
  res.json({ success: true });
242
  } catch (err: any) {
243
  res.status(500).json({ error: err.message });
244
  }
245
  });
246
 
247
- // ✅ فك حظر بريد: إعادة تفعيل المستخدمين المحظورين
248
  router.delete("/blocked-emails/:email", async (req: Request, res: Response) => {
249
  const owner = await requireOwner(req, res);
250
  if (!owner) return;
251
  try {
252
  const email = decodeURIComponent(req.params.email).toLowerCase();
253
-
254
- // حذف من جدول الحظر
255
  await db.delete(blockedEmailsTable).where(eq(blockedEmailsTable.email, email));
256
-
257
- // ✅ إعادة تفعيل المستخدمين المحظورين بنفس البريد
258
- try {
259
- const bannedUsers = await clerkClient.users.getUserList({ emailAddress: [email], limit: 5 });
260
- for (const u of bannedUsers.data) {
261
- try { await clerkClient.users.unbanUser(u.id); } catch { /* ignore */ }
262
- }
263
- } catch { /* ignore */ }
264
-
265
- res.json({ success: true, message: "تم فك الحظر - يمكن للمستخدم التسجيل من جديد" });
266
  } catch (err: any) {
267
  res.status(500).json({ error: err.message });
268
  }
269
  });
270
 
271
- // ✅ حظر مؤقت: banUser (صحيح)
272
  router.post("/ban/:userId", async (req: Request, res: Response) => {
273
  const owner = await requireOwner(req, res);
274
  if (!owner) return;
@@ -277,28 +246,19 @@ router.post("/ban/:userId", async (req: Request, res: Response) => {
277
  await db
278
  .insert(usersTable)
279
  .values({ id: userId, isBanned: true, bannedAt: new Date() })
280
- .onConflictDoUpdate({
281
- target: usersTable.id,
282
- set: { isBanned: true, bannedAt: new Date() },
283
- });
284
- try { await clerkClient.users.banUser(userId); } catch { /* ignore */ }
285
  res.json({ success: true });
286
  } catch (err: any) {
287
  res.status(500).json({ error: err.message });
288
  }
289
  });
290
 
291
- // ✅ فك حظر مؤقت: unbanUser (صحيح)
292
  router.post("/unban/:userId", async (req: Request, res: Response) => {
293
  const owner = await requireOwner(req, res);
294
  if (!owner) return;
295
  const { userId } = req.params;
296
  try {
297
- await db
298
- .update(usersTable)
299
- .set({ isBanned: false, bannedAt: null })
300
- .where(eq(usersTable.id, userId));
301
- try { await clerkClient.users.unbanUser(userId); } catch { /* ignore */ }
302
  res.json({ success: true });
303
  } catch (err: any) {
304
  res.status(500).json({ error: err.message });
@@ -309,10 +269,7 @@ router.post("/force-signout-banned", async (req: Request, res: Response) => {
309
  const owner = await requireOwner(req, res);
310
  if (!owner) return;
311
  try {
312
- const banned = await db
313
- .select({ id: usersTable.id })
314
- .from(usersTable)
315
- .where(eq(usersTable.isBanned, true));
316
  const ids = banned.map((u) => u.id);
317
  const sent = broadcastToUsers(ids, "forceSignOut", { reason: "banned" });
318
  res.json({ success: true, usersNotified: ids.length, connectionsSent: sent });
@@ -326,19 +283,11 @@ router.post("/cleanup-orphans", async (req: Request, res: Response) => {
326
  if (!owner) return;
327
  try {
328
  const dbUsers = await db.select({ id: usersTable.id, email: usersTable.email }).from(usersTable);
329
-
330
  const removed: string[] = [];
331
 
332
  for (const u of dbUsers) {
333
- let existsInClerk = false;
334
- try {
335
- const cu = await clerkClient.users.getUser(u.id);
336
- if (cu?.id) existsInClerk = true;
337
- } catch {
338
- existsInClerk = false;
339
- }
340
-
341
- if (!existsInClerk) {
342
  await db.delete(usersTable).where(eq(usersTable.id, u.id));
343
  removed.push(u.email ?? u.id);
344
  }
 
1
  import { Router, type Request, type Response } from "express";
2
+ import { createClient } from "@supabase/supabase-js";
3
  import { db, usersTable, subscriptionsTable, appConfigTable, blockedEmailsTable } from "@workspace/db";
 
4
  import { eq } from "drizzle-orm";
5
  import { broadcastToUsers } from "../../lib/sseManager";
6
 
7
  const router = Router();
8
 
9
+ const supabase = createClient(
10
+ process.env.SUPABASE_URL || "",
11
+ process.env.SUPABASE_SERVICE_ROLE_KEY || ""
12
+ );
13
+
14
  const OWNER_EMAIL = "saresare2555@hotmail.com";
15
 
16
  async function requireOwner(req: Request, res: Response): Promise<string | null> {
17
+ const authHeader = req.headers.authorization;
18
+ if (!authHeader?.startsWith("Bearer ")) {
 
19
  res.status(401).json({ error: "غير مصرح" });
20
  return null;
21
  }
22
+
23
+ const token = authHeader.split(" ")[1];
24
+ const { data: { user }, error } = await supabase.auth.getUser(token);
25
+
26
+ if (error || !user) {
27
  res.status(401).json({ error: "فشل التحقق من الهوية" });
28
  return null;
29
  }
30
+
31
+ const email = user.email?.toLowerCase();
32
+ if (email !== OWNER_EMAIL.toLowerCase()) {
33
  res.status(403).json({ error: "ممنوع — للمالك فقط" });
34
  return null;
35
  }
36
+
37
+ return user.id;
38
  }
39
 
40
  router.get("/users", async (req: Request, res: Response) => {
 
42
  if (!owner) return;
43
 
44
  try {
45
+ // جلب كل مستخدمي Supabase Auth
46
+ const { data: { users: authUsers }, error } = await supabase.auth.admin.listUsers();
47
+ if (error) throw error;
48
+
49
+ const dbUsers = await db
50
+ .select({
51
+ id: usersTable.id,
52
+ email: usersTable.email,
53
+ ipAddress: usersTable.ipAddress,
54
+ lastSeenAt: usersTable.lastSeenAt,
55
+ isBanned: usersTable.isBanned,
56
+ bannedAt: usersTable.bannedAt
57
+ })
58
+ .from(usersTable);
59
+
60
  const dbSubs = await db.select().from(subscriptionsTable);
61
 
62
  const dbMap = new Map(dbUsers.map((u) => [u.id, u]));
63
  const subMap = new Map(dbSubs.map((s) => [s.userId, s]));
64
 
65
+ const users = authUsers.map((au) => {
66
+ const email = au.email ?? null;
67
+ const sub = subMap.get(au.id);
68
+ const dbUser = dbMap.get(au.id);
69
+
70
  return {
71
+ id: au.id,
72
  email,
73
  ipAddress: dbUser?.ipAddress ?? null,
74
  lastSeenAt: dbUser?.lastSeenAt ? dbUser.lastSeenAt.toISOString() : null,
75
  isBanned: dbUser?.isBanned ?? false,
76
  bannedAt: dbUser?.bannedAt ? dbUser.bannedAt.toISOString() : null,
77
+ createdAt: new Date(au.created_at).toISOString(),
78
+ lastSignIn: au.last_sign_in_at ? new Date(au.last_sign_in_at).toISOString() : null,
79
  subscription: sub
80
  ? {
81
  status: sub.status,
 
83
  currentPeriodEnd: sub.currentPeriodEnd?.toISOString() ?? null,
84
  }
85
  : null,
86
+ inDb: dbMap.has(au.id),
87
  };
88
  });
89
 
 
105
  };
106
 
107
  try {
 
108
  await db
109
  .insert(usersTable)
110
  .values({ id: userId, isBanned: false })
111
+ .onConflictDoUpdate({ target: usersTable.id, set: { id: userId } });
 
 
 
112
 
 
113
  const updates: Record<string, unknown> = { updatedAt: new Date() };
114
  if (status !== undefined) updates.status = status;
115
  if (trialEnd !== undefined) updates.trialEnd = trialEnd ? new Date(trialEnd) : null;
116
  if (currentPeriodEnd !== undefined) updates.currentPeriodEnd = currentPeriodEnd ? new Date(currentPeriodEnd) : null;
117
 
 
118
  const result = await db
119
  .update(subscriptionsTable)
120
  .set(updates)
121
  .where(eq(subscriptionsTable.userId, userId));
122
 
 
123
  if (result.rowCount === 0) {
124
  await db.insert(subscriptionsTable).values({
125
  userId,
 
140
  const owner = await requireOwner(req, res);
141
  if (!owner) return;
142
  try {
143
+ await db.delete(appConfigTable).where(eq(appConfigTable.key, "paypal_product_id"));
144
+ await db.delete(appConfigTable).where(eq(appConfigTable.key, "paypal_plan_id_v2"));
145
+ await db.delete(appConfigTable).where(eq(appConfigTable.key, "paypal_plan_id"));
146
+ res.json({ success: true, message: "تم مسح إعدادات PayPal" });
 
147
  } catch (err: any) {
148
  res.status(500).json({ error: err.message });
149
  }
 
179
  }
180
  });
181
 
 
182
  router.delete("/users/:id", async (req: Request, res: Response) => {
183
  const owner = await requireOwner(req, res);
184
  if (!owner) return;
 
185
  const targetId = req.params.id;
186
  if (!targetId) {
187
  res.status(400).json({ error: "معرف المستخدم مطلوب" });
188
  return;
189
  }
 
190
  try {
191
  await db.delete(subscriptionsTable).where(eq(subscriptionsTable.userId, targetId));
192
  await db.delete(usersTable).where(eq(usersTable.id, targetId));
193
+ res.json({ success: true, message: م حذف المستخدم" });
 
 
194
  } catch (err: any) {
195
  res.status(500).json({ error: err.message });
196
  }
 
207
  }
208
  });
209
 
 
210
  router.post("/blocked-emails", async (req: Request, res: Response) => {
211
  const owner = await requireOwner(req, res);
212
  if (!owner) return;
 
217
  }
218
  try {
219
  const normalized = email.toLowerCase().trim();
 
 
220
  await db.insert(blockedEmailsTable)
221
  .values({ email: normalized, reason: reason ?? null })
222
+ .onConflictDoUpdate({ target: blockedEmailsTable.email, set: { reason: reason ?? null } });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  res.json({ success: true });
224
  } catch (err: any) {
225
  res.status(500).json({ error: err.message });
226
  }
227
  });
228
 
 
229
  router.delete("/blocked-emails/:email", async (req: Request, res: Response) => {
230
  const owner = await requireOwner(req, res);
231
  if (!owner) return;
232
  try {
233
  const email = decodeURIComponent(req.params.email).toLowerCase();
 
 
234
  await db.delete(blockedEmailsTable).where(eq(blockedEmailsTable.email, email));
235
+ res.json({ success: true });
 
 
 
 
 
 
 
 
 
236
  } catch (err: any) {
237
  res.status(500).json({ error: err.message });
238
  }
239
  });
240
 
 
241
  router.post("/ban/:userId", async (req: Request, res: Response) => {
242
  const owner = await requireOwner(req, res);
243
  if (!owner) return;
 
246
  await db
247
  .insert(usersTable)
248
  .values({ id: userId, isBanned: true, bannedAt: new Date() })
249
+ .onConflictDoUpdate({ target: usersTable.id, set: { isBanned: true, bannedAt: new Date() } });
 
 
 
 
250
  res.json({ success: true });
251
  } catch (err: any) {
252
  res.status(500).json({ error: err.message });
253
  }
254
  });
255
 
 
256
  router.post("/unban/:userId", async (req: Request, res: Response) => {
257
  const owner = await requireOwner(req, res);
258
  if (!owner) return;
259
  const { userId } = req.params;
260
  try {
261
+ await db.update(usersTable).set({ isBanned: false, bannedAt: null }).where(eq(usersTable.id, userId));
 
 
 
 
262
  res.json({ success: true });
263
  } catch (err: any) {
264
  res.status(500).json({ error: err.message });
 
269
  const owner = await requireOwner(req, res);
270
  if (!owner) return;
271
  try {
272
+ const banned = await db.select({ id: usersTable.id }).from(usersTable).where(eq(usersTable.isBanned, true));
 
 
 
273
  const ids = banned.map((u) => u.id);
274
  const sent = broadcastToUsers(ids, "forceSignOut", { reason: "banned" });
275
  res.json({ success: true, usersNotified: ids.length, connectionsSent: sent });
 
283
  if (!owner) return;
284
  try {
285
  const dbUsers = await db.select({ id: usersTable.id, email: usersTable.email }).from(usersTable);
 
286
  const removed: string[] = [];
287
 
288
  for (const u of dbUsers) {
289
+ const { data, error } = await supabase.auth.admin.getUserById(u.id);
290
+ if (error || !data?.user) {
 
 
 
 
 
 
 
291
  await db.delete(usersTable).where(eq(usersTable.id, u.id));
292
  removed.push(u.email ?? u.id);
293
  }