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 { 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 = { 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)); // 🆕 حذف من Supabase Auth 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 }); } }); // ✅ حظر مستخدم - مع Supabase Auth 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() } }); // 🆕 حظر المستخدم في Supabase Auth await supabase.auth.admin.updateUserById(userId, { ban_duration: "876000h" // 100 سنة }); res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ✅ رفع الحظر - مع Supabase Auth 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)); // 🆕 رفع الحظر من Supabase Auth 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;