sare26's picture
Update artifacts/api-server/src/routes/admin/index.ts
97acd72 verified
Raw
History Blame Contribute Delete
10.8 kB
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));
// 🆕 حذف من 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;