import { Router } from "express"; import { db, configTable, geminiAccountsTable } from "@workspace/db"; import { eq, sql } from "drizzle-orm"; import { OTP_KEY, SITE_CONFIG_KEYS } from "./admin"; const router = Router(); const TOKEN_KEY = "geminigen_bearer_token"; const REFRESH_TOKEN_KEY = "geminigen_refresh_token"; router.post("/receive-tokens", async (req, res) => { const { otp, access_token, refresh_token, label } = req.body as { otp?: string; access_token?: string; refresh_token?: string; label?: string; }; if (!otp || !access_token) { return res.status(400).json({ error: "otp 和 access_token 為必填" }); } const row = await db .select() .from(configTable) .where(eq(configTable.key, OTP_KEY)) .limit(1); if (!row.length) { return res.status(401).json({ error: "OTP 無效或已過期,請重新產生書籤" }); } const [storedOtp, expiresAtStr] = row[0].value.split(":"); const expiresAt = Number(expiresAtStr); if (storedOtp !== otp || Date.now() > expiresAt) { return res.status(401).json({ error: "OTP 無效或已過期,請重新產生書籤" }); } // Delete OTP (one-time use) await db.delete(configTable).where(eq(configTable.key, OTP_KEY)); if (label) { // Save to pool as a new account await db.insert(geminiAccountsTable).values({ label: label.trim(), bearerToken: access_token, refreshToken: refresh_token || null, isActive: true, }); return res.json({ success: true, message: `帳戶「${label}」已加入 Token 池!` }); } // Legacy: save as single token in config await db .insert(configTable) .values({ key: TOKEN_KEY, value: access_token, updatedAt: new Date() }) .onConflictDoUpdate({ target: configTable.key, set: { value: access_token, updatedAt: new Date() } }); if (refresh_token) { await db .insert(configTable) .values({ key: REFRESH_TOKEN_KEY, value: refresh_token, updatedAt: new Date() }) .onConflictDoUpdate({ target: configTable.key, set: { value: refresh_token, updatedAt: new Date() } }); } return res.json({ success: true, message: "Token 已成功同步!" }); }); // ── Public site config (logo, Google Ads) ───────────────────────────────────── const PUBLIC_SITE_KEYS = ["logo_url", "site_name", "google_ads_enabled", "google_ads_client", "google_ads_slot"]; router.get("/site-config", async (_req, res) => { const rows = await db .select() .from(configTable) .where(sql`${configTable.key} = ANY(ARRAY[${sql.raw(PUBLIC_SITE_KEYS.map(k => `'${k}'`).join(","))}]::text[])`); const config: Record = {}; for (const row of rows) config[row.key] = row.value; res.json(config); }); export default router;