| import { Router, type Request, type Response } from "express"; |
| import { createClient } from "@supabase/supabase-js"; |
| import { db, usersTable, subscriptionsTable, appConfigTable, blockedEmailsTable } from "@workspace/db"; |
| import { eq } from "drizzle-orm"; |
| import { broadcastToUsers } from "../../lib/sseManager"; |
|
|
| const router = Router(); |
|
|
| const supabase = createClient( |
| process.env.SUPABASE_URL || "", |
| process.env.SUPABASE_SERVICE_ROLE_KEY || "" |
| ); |
|
|
| const OWNER_EMAIL = "saresare2555@hotmail.com"; |
|
|
| async function requireOwner(req: Request, res: Response): Promise<string | null> { |
| const authHeader = req.headers.authorization; |
| if (!authHeader?.startsWith("Bearer ")) { |
| res.status(401).json({ error: "غير مصرح" }); |
| return null; |
| } |
|
|
| const token = authHeader.split(" ")[1]; |
| const { data: { user }, error } = await supabase.auth.getUser(token); |
|
|
| if (error || !user) { |
| res.status(401).json({ error: "فشل التحقق من الهوية" }); |
| return null; |
| } |
|
|
| const email = user.email?.toLowerCase(); |
| if (email !== OWNER_EMAIL.toLowerCase()) { |
| res.status(403).json({ error: "ممنوع — للمالك فقط" }); |
| return null; |
| } |
|
|
| return user.id; |
| } |
|
|
| router.get("/users", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
|
|
| try { |
| const { data: { users: authUsers }, error } = await supabase.auth.admin.listUsers(); |
| if (error) throw error; |
|
|
| 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); |
|
|
| const dbSubs = await db.select().from(subscriptionsTable); |
|
|
| const dbMap = new Map(dbUsers.map((u) => [u.id, u])); |
| const subMap = new Map(dbSubs.map((s) => [s.userId, s])); |
|
|
| const users = authUsers.map((au) => { |
| const email = au.email ?? null; |
| const sub = subMap.get(au.id); |
| const dbUser = dbMap.get(au.id); |
|
|
| return { |
| id: au.id, |
| email, |
| ipAddress: dbUser?.ipAddress ?? null, |
| lastSeenAt: dbUser?.lastSeenAt ? dbUser.lastSeenAt.toISOString() : null, |
| isBanned: dbUser?.isBanned ?? false, |
| bannedAt: dbUser?.bannedAt ? dbUser.bannedAt.toISOString() : null, |
| createdAt: new Date(au.created_at).toISOString(), |
| lastSignIn: au.last_sign_in_at ? new Date(au.last_sign_in_at).toISOString() : null, |
| subscription: sub |
| ? { |
| status: sub.status, |
| trialEnd: sub.trialEnd?.toISOString() ?? null, |
| currentPeriodEnd: sub.currentPeriodEnd?.toISOString() ?? null, |
| } |
| : null, |
| inDb: dbMap.has(au.id), |
| }; |
| }); |
|
|
| res.json({ users }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.patch("/subscription/:userId", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
|
|
| const { userId } = req.params; |
| const { status, trialEnd, currentPeriodEnd } = req.body as { |
| status?: string; |
| trialEnd?: string | null; |
| currentPeriodEnd?: string | null; |
| }; |
|
|
| try { |
| await db |
| .insert(usersTable) |
| .values({ id: userId, isBanned: false }) |
| .onConflictDoUpdate({ target: usersTable.id, set: { id: userId } }); |
|
|
| const updates: Record<string, unknown> = { updatedAt: new Date() }; |
| if (status !== undefined) updates.status = status; |
| if (trialEnd !== undefined) updates.trialEnd = trialEnd ? new Date(trialEnd) : null; |
| if (currentPeriodEnd !== undefined) updates.currentPeriodEnd = currentPeriodEnd ? new Date(currentPeriodEnd) : null; |
|
|
| const result = await db |
| .update(subscriptionsTable) |
| .set(updates) |
| .where(eq(subscriptionsTable.userId, userId)); |
|
|
| if (result.rowCount === 0) { |
| await db.insert(subscriptionsTable).values({ |
| userId, |
| status: status || "trialing", |
| trialEnd: trialEnd ? new Date(trialEnd) : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), |
| currentPeriodEnd: currentPeriodEnd ? new Date(currentPeriodEnd) : null, |
| updatedAt: new Date(), |
| }); |
| } |
|
|
| res.json({ success: true }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.delete("/paypal-config", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| try { |
| await db.delete(appConfigTable).where(eq(appConfigTable.key, "paypal_product_id")); |
| await db.delete(appConfigTable).where(eq(appConfigTable.key, "paypal_plan_id_v2")); |
| await db.delete(appConfigTable).where(eq(appConfigTable.key, "paypal_plan_id")); |
| res.json({ success: true, message: "تم مسح إعدادات PayPal" }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.patch("/extension-update", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
|
|
| const { enabled, version, message } = req.body as { |
| enabled: boolean; |
| version?: string; |
| message?: string; |
| }; |
|
|
| try { |
| async function upsert(key: string, value: string) { |
| const [existing] = await db.select().from(appConfigTable).where(eq(appConfigTable.key, key)); |
| if (existing) { |
| await db.update(appConfigTable).set({ value }).where(eq(appConfigTable.key, key)); |
| } else { |
| await db.insert(appConfigTable).values({ key, value }); |
| } |
| } |
|
|
| await upsert("extension_update_available", enabled ? "true" : "false"); |
| if (version !== undefined) await upsert("extension_update_version", version); |
| if (message !== undefined) await upsert("extension_update_message", message); |
|
|
| res.json({ success: true }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.delete("/users/:id", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
|
|
| const targetId = req.params.id; |
| if (!targetId) { |
| res.status(400).json({ error: "معرف المستخدم مطلوب" }); |
| return; |
| } |
|
|
| try { |
| await db.delete(subscriptionsTable).where(eq(subscriptionsTable.userId, targetId)); |
| await db.delete(usersTable).where(eq(usersTable.id, targetId)); |
| |
| |
| await supabase.auth.admin.deleteUser(targetId); |
| |
| res.json({ success: true, message: "تم حذف المستخدم" }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.get("/blocked-emails", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| try { |
| const rows = await db.select().from(blockedEmailsTable).orderBy(blockedEmailsTable.blockedAt); |
| res.json({ blockedEmails: rows }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.post("/blocked-emails", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| const { email, reason } = req.body as { email: string; reason?: string }; |
| if (!email || email.trim().length < 3) { |
| res.status(400).json({ error: "القيمة غير صالحة" }); |
| return; |
| } |
| try { |
| const normalized = email.toLowerCase().trim(); |
| await db.insert(blockedEmailsTable) |
| .values({ email: normalized, reason: reason ?? null }) |
| .onConflictDoUpdate({ target: blockedEmailsTable.email, set: { reason: reason ?? null } }); |
| res.json({ success: true }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.delete("/blocked-emails/:email", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| try { |
| const email = decodeURIComponent(req.params.email).toLowerCase(); |
| await db.delete(blockedEmailsTable).where(eq(blockedEmailsTable.email, email)); |
| res.json({ success: true }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| |
| router.post("/ban/:userId", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| const { userId } = req.params; |
| try { |
| await db |
| .insert(usersTable) |
| .values({ id: userId, isBanned: true, bannedAt: new Date() }) |
| .onConflictDoUpdate({ target: usersTable.id, set: { isBanned: true, bannedAt: new Date() } }); |
|
|
| |
| await supabase.auth.admin.updateUserById(userId, { |
| ban_duration: "876000h" |
| }); |
|
|
| res.json({ success: true }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| |
| router.post("/unban/:userId", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| const { userId } = req.params; |
| try { |
| await db.update(usersTable).set({ isBanned: false, bannedAt: null }).where(eq(usersTable.id, userId)); |
|
|
| |
| await supabase.auth.admin.updateUserById(userId, { |
| ban_duration: "none" |
| }); |
|
|
| res.json({ success: true }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.post("/force-signout-banned", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| try { |
| const banned = await db.select({ id: usersTable.id }).from(usersTable).where(eq(usersTable.isBanned, true)); |
| const ids = banned.map((u) => u.id); |
| const sent = broadcastToUsers(ids, "forceSignOut", { reason: "banned" }); |
| res.json({ success: true, usersNotified: ids.length, connectionsSent: sent }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| router.post("/cleanup-orphans", async (req: Request, res: Response) => { |
| const owner = await requireOwner(req, res); |
| if (!owner) return; |
| try { |
| const dbUsers = await db.select({ id: usersTable.id, email: usersTable.email }).from(usersTable); |
| const removed: string[] = []; |
|
|
| for (const u of dbUsers) { |
| const { data, error } = await supabase.auth.admin.getUserById(u.id); |
| if (error || !data?.user) { |
| await db.delete(usersTable).where(eq(usersTable.id, u.id)); |
| removed.push(u.email ?? u.id); |
| } |
| } |
|
|
| res.json({ removed, count: removed.length }); |
| } catch (err: any) { |
| res.status(500).json({ error: err.message }); |
| } |
| }); |
|
|
| export default router; |