2026-03-28 01:53:33 +00:00

220 lines
6.7 KiB
TypeScript

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<string, number>();
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<ReturnType<typeof fetchSheinCategories>> | 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<number>`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<number>`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;