"use server"; import { getAdminUser } from "@/lib/admin-permissions"; import { withBrand } from "@/db/client"; import { products } from "@/db/schema"; import { getSession } from "@/lib/auth"; export type ImportProductsResult = | { success: true; created: number; updated: number; errors: { product: string; error: string }[] } | { success: false; error: string }; /** * Bulk-import products. Replaces the legacy `bulk_upsert_products` SECURITY * DEFINER RPC. The new `products` schema drops the legacy `type`, `is_taxable`, * `pickup_type`, and `image_url` columns; we keep `name`, `description`, * `price_cents`, and `active`. Without an id we always INSERT (no upsert * key for matching — the caller can run an update path separately if * deduplication is needed). */ export async function importProductsBatch( brandId: string, productsToImport: Array<{ name: string; description: string; price: number; type: string; active: boolean; image_url?: string; }> ): Promise { await getSession(); const adminUser = await getAdminUser(); if (!adminUser) return { success: false, error: "Not authenticated" }; if (!adminUser.can_manage_products) return { success: false, error: "Not authorized" }; if (adminUser.role === "brand_admin" && adminUser.brand_id !== brandId) { return { success: false, error: "Not authorized for this brand" }; } let created = 0; const errors: { product: string; error: string }[] = []; const validProducts: Array<{ name: string; description: string; price: number; type: string; active: boolean; image_url?: string }> = []; const skipped: { product: { name: string; description: string; price: number; type: string; active: boolean; image_url?: string }; error: string }[] = []; for (const p of productsToImport) { const priceCents = Math.round(Number(p.price) * 100); if (!Number.isFinite(priceCents) || priceCents < 0) { skipped.push({ product: p, error: "Invalid price" }); } else { validProducts.push(p); } } const settled = await Promise.allSettled( validProducts.map((p) => withBrand(brandId, (db) => db.insert(products).values({ brandId: brandId, name: p.name, description: p.description ?? null, priceCents: Math.round(Number(p.price) * 100), active: p.active, }), ).then( () => p, (err) => ({ p, err }), ), ), ); for (const entry of settled) { if (entry.status === "rejected") { const err = (entry as PromiseRejectedResult).reason; errors.push({ product: "", error: err instanceof Error ? err.message : String(err), }); continue; } const value = entry.value; if ("err" in value) { errors.push({ product: value.p.name, error: value.err instanceof Error ? value.err.message : String(value.err), }); } else { created++; } } for (const s of skipped) { errors.push({ product: s.product.name, error: s.error }); } return { success: true, created, updated: 0, errors }; }