import { getDb, closeDb } from "./index"; import { providerConfigs, featureFlags, organizations, signupAllowlist, users } from "./schema"; import { and, eq, ne, sql } from "drizzle-orm"; /** Emails from `ADMIN_EMAILS` (comma-separated, case-insensitive). */ function adminEmails(): string[] { return Array.from( new Set( (process.env.ADMIN_EMAILS ?? "") .split(",") .map((s) => s.trim().toLowerCase()) .filter(Boolean), ), ); } async function main() { const db = getDb(); const providers: Array<{ id: string; label: string; networks: string[]; pricePerGbUsd: Record; weight: number; maxConcurrency: number }> = [ { id: "oxylabs", label: "Network A (residential)", networks: ["residential"], pricePerGbUsd: { residential: 8 }, weight: 1, maxConcurrency: 300 }, { id: "decodo", label: "Network B (residential)", networks: ["residential"], pricePerGbUsd: { residential: 7 }, weight: 1, maxConcurrency: 300 }, { id: "soax", label: "Network C (residential, mobile)", networks: ["residential", "mobile"], pricePerGbUsd: { residential: 9, mobile: 15 }, weight: 0.8, maxConcurrency: 200 }, ]; for (const p of providers) { await db .insert(providerConfigs) .values(p) .onConflictDoUpdate({ target: providerConfigs.id, set: { label: p.label, networks: p.networks, pricePerGbUsd: p.pricePerGbUsd, updatedAt: sql`now()` } }); } const flags = [ { key: "browser_enabled", enabled: true, description: "Managed browser rendering (live)" }, { key: "extract_enabled", enabled: false, description: "Structured extraction endpoint /v1/extract" }, { key: "mobile_proxy_enabled", enabled: false, description: "Mobile network class" }, { key: "organizations_enabled", enabled: false, description: "Team organizations UI" }, { key: "billing_enabled", enabled: false, description: "Stripe checkout & subscriptions" }, ]; for (const f of flags) { await db.insert(featureFlags).values(f).onConflictDoNothing(); } // Single-plan platform: defensively normalize any legacy plan value (migration 0001 already does this). const normalized = await db.update(organizations).set({ plan: "unlimited", updatedAt: sql`now()` }).where(ne(organizations.plan, "unlimited")).returning({ id: organizations.id }); if (normalized.length) console.log(`[db] ${normalized.length} organization(s) moved to plan=unlimited`); // Administrators: allowlisted + promoted (idempotent). const admins = adminEmails(); if (!admins.length) { console.warn("[db] ADMIN_EMAILS is empty — no administrator allowlisted or promoted"); } for (const email of admins) { await db.insert(signupAllowlist).values({ email, note: "administrator" }).onConflictDoNothing(); const promoted = await db .update(users) .set({ role: "admin", updatedAt: sql`now()` }) .where(and(sql`lower(${users.email}) = ${email}`, ne(users.role, "admin"))) .returning({ id: users.id }); if (promoted.length) console.log(`[db] promoted ${email} to admin`); const [existing] = await db.select({ id: users.id, createdAt: users.createdAt }).from(users).where(sql`lower(${users.email}) = ${email}`).limit(1); if (existing) { await db .update(signupAllowlist) .set({ userId: existing.id, usedAt: sql`coalesce(${signupAllowlist.usedAt}, ${existing.createdAt})` }) .where(eq(signupAllowlist.email, email)); } console.log(`[db] admin ${email}: allowlisted${existing ? ", account linked" : ", no account yet"}`); } // v0.2: the managed browser is live. Rows seeded by v0.1 (still carrying the old description) are // switched on once; an admin toggling the flag off afterwards is respected on later seeds. await db.execute(sql`update feature_flags set enabled = true, description = 'Managed browser rendering (live)', updated_at = now() where key = 'browser_enabled' and description = 'Managed browser execution (/v1/fetch browser=true)'`); console.log("[db] seed ok"); await closeDb(); } main().catch((e) => { console.error(e); process.exit(1); });