import { Router, type IRouter } from "express"; import { runAllIntegrationTests, testRapidApi, testSerpApi, testCloudinary, testApify, testDatabase } from "../lib/integration-tests"; import { validateServicesConfig } from "../config/services"; import { fetchSheinCategories, SHEIN_CATEGORIES_PRESET, type SheinCategory } from "../lib/shein-scraper"; import { db, categoriesTable } from "@workspace/db"; import { eq, sql } from "drizzle-orm"; import { requireAdmin } from "../middleware/auth"; const router: IRouter = Router(); router.get("/integrations/status", async (_req, res) => { try { const configCheck = validateServicesConfig(); const results = await runAllIntegrationTests(); const allConnected = results.every(r => r.connected); res.json({ ready: allConnected, config: configCheck, services: results, summary: results.map(r => `${r.connected ? "✅" : "❌"} ${r.service}: ${r.message}`), }); } catch (err: unknown) { const errMsg = err instanceof Error ? err.message : String(err); res.status(500).json({ error: errMsg }); } }); router.get("/integrations/rapidapi", async (_req, res) => { res.json(await testRapidApi()); }); router.get("/integrations/serpapi", async (_req, res) => { res.json(await testSerpApi()); }); router.get("/integrations/cloudinary", async (_req, res) => { res.json(await testCloudinary()); }); router.get("/integrations/apify", async (_req, res) => { res.json(await testApify()); }); router.get("/integrations/database", async (_req, res) => { res.json(await testDatabase()); }); async function saveSheinCategoriesToDb(categories: SheinCategory[]): Promise<{ saved: number; errors: number }> { let saved = 0; let errors = 0; const mainCats = categories.filter(c => c.level === 1); const subCats = categories.filter(c => c.level === 2); const parentIdMap = new Map(); for (const cat of mainCats) { try { const existing = await db .select({ id: categoriesTable.id }) .from(categoriesTable) .where(eq(categoriesTable.slug, cat.slug)) .limit(1); if (existing.length > 0) { await db.update(categoriesTable).set({ name: cat.name_ar, name_en: cat.name_en, icon: cat.icon ?? null, sort_order: cat.sort_order, source: "shein", shein_cat_id: cat.shein_cat_id, shein_url: cat.shein_url, slug: cat.slug, parent_id: null, }).where(eq(categoriesTable.id, existing[0]!.id)); parentIdMap.set(cat.slug, existing[0]!.id); saved++; } else { const [inserted] = await db.insert(categoriesTable).values({ name: cat.name_ar, name_en: cat.name_en, slug: cat.slug, icon: cat.icon ?? null, sort_order: cat.sort_order, parent_id: null, source: "shein", shein_cat_id: cat.shein_cat_id, shein_url: cat.shein_url, }).returning({ id: categoriesTable.id }); if (inserted) { parentIdMap.set(cat.slug, inserted.id); saved++; } } } catch { errors++; } } for (const cat of subCats) { try { const parentId = cat.parent_slug ? parentIdMap.get(cat.parent_slug) : undefined; const existing = await db .select({ id: categoriesTable.id }) .from(categoriesTable) .where(eq(categoriesTable.slug, cat.slug)) .limit(1); if (existing.length > 0) { await db.update(categoriesTable).set({ name: cat.name_ar, name_en: cat.name_en, slug: cat.slug, sort_order: cat.sort_order, parent_id: parentId ?? null, source: "shein", shein_cat_id: cat.shein_cat_id, shein_url: cat.shein_url, }).where(eq(categoriesTable.id, existing[0]!.id)); saved++; } else { await db.insert(categoriesTable).values({ name: cat.name_ar, name_en: cat.name_en, slug: cat.slug, sort_order: cat.sort_order, parent_id: parentId ?? null, source: "shein", shein_cat_id: cat.shein_cat_id, shein_url: cat.shein_url, }); saved++; } } catch { errors++; } } return { saved, errors }; } router.get("/integrations/shein-categories", async (req, res) => { const mode = (req.query["mode"] as string) ?? "preset"; try { let categories: SheinCategory[] = []; let source = "preset"; let scrapeResult: Awaited> | null = null; if (mode === "scrape") { scrapeResult = await fetchSheinCategories(); if (scrapeResult.success && scrapeResult.categories && scrapeResult.categories.length > 0) { categories = scrapeResult.categories; source = "apify-scrape"; } else { categories = SHEIN_CATEGORIES_PRESET; source = "preset-fallback"; } } else { categories = SHEIN_CATEGORIES_PRESET; } const dbTotal = await db .select({ count: sql`count(*)::int` }) .from(categoriesTable) .where(eq(categoriesTable.source, "shein")); const sections = categories.filter(c => c.level === 1); const subcats = categories.filter(c => c.level === 2); res.json({ success: true, source, totalCategories: categories.length, sections: sections.length, subcategories: subcats.length, dbTotal: dbTotal[0]?.count ?? 0, scrapeResult: scrapeResult ? { success: scrapeResult.success, error: scrapeResult.error, runId: scrapeResult.runId, } : null, data: { sections: sections.map(s => ({ ...s, subcategories: subcats.filter(sc => sc.parent_slug === s.slug), })), }, }); } catch (err: unknown) { const errMsg = err instanceof Error ? err.message : String(err); res.status(500).json({ error: errMsg }); } }); router.post("/integrations/shein-categories/save", requireAdmin, async (_req, res) => { try { const dbResult = await saveSheinCategoriesToDb(SHEIN_CATEGORIES_PRESET); const total = await db .select({ count: sql`count(*)::int` }) .from(categoriesTable) .where(eq(categoriesTable.source, "shein")); res.json({ success: true, saved: dbResult.saved, errors: dbResult.errors, totalInDb: total[0]?.count ?? 0, message: `تم حفظ ${dbResult.saved} فئة من متجر Shein في قاعدة البيانات`, }); } catch (err: unknown) { const errMsg = err instanceof Error ? err.message : String(err); res.status(500).json({ error: errMsg }); } }); export default router;