/** * Seed Hamkar defaultSnapshot from users.json (original registry look). * Optionally restore current appearance to that snapshot (--restore). * * Usage: * npx ts-node --transpile-only prisma/seed-hamkar-default-snapshots.ts * npx ts-node --transpile-only prisma/seed-hamkar-default-snapshots.ts --restore * npx ts-node --transpile-only prisma/seed-hamkar-default-snapshots.ts --restore --domain=montpar.ir */ import * as fs from 'fs'; import * as path from 'path'; import { PrismaClient, ProductType } from '@prisma/client'; import { buildHamkarUiFromBrand } from './lib/appearance-builders'; const prisma = new PrismaClient(); type SourceUser = { id: number; name?: string | null; name_fa?: string | null; domain?: string | null; domains?: string | string[] | null; api_origin?: string | null; mode?: string | null; theme?: string | null; color?: string | null; secondary_color?: string | null; background_color?: string | null; deleted_at?: string | null; }; function parseArgs() { const restore = process.argv.includes('--restore'); const domainArg = process.argv.find((a) => a.startsWith('--domain=')); const domain = domainArg ? domainArg.slice('--domain='.length).trim().toLowerCase() : null; return { restore, domain }; } function parseDomains(raw: SourceUser['domains']): string[] { if (!raw) return []; try { const value = typeof raw === 'string' ? JSON.parse(raw) : raw; if (!Array.isArray(value)) return []; return value .map((item) => String(item).trim().toLowerCase()) .filter(Boolean) .map((item) => item.replace(/^https?:\/\//, '').split('/')[0]); } catch { return []; } } function primaryDomain(row: SourceUser): string | null { const fromField = row.domain?.trim().toLowerCase(); if (fromField) return fromField.replace(/^https?:\/\//, '').split('/')[0]; const fromOrigin = row.api_origin?.trim().toLowerCase(); if (fromOrigin) return fromOrigin.replace(/^https?:\/\//, '').split('/')[0]; const domains = parseDomains(row.domains); return domains[0] || null; } function emptyToNull(v: string | null | undefined) { const s = String(v ?? '').trim(); if (!s || s === 'null' || s === 'undefined') return null; return s; } async function main() { const { restore, domain: onlyDomain } = parseArgs(); const file = path.join(__dirname, 'data', 'users.json'); if (!fs.existsSync(file)) { throw new Error(`Missing ${file}`); } const rows = JSON.parse(fs.readFileSync(file, 'utf8')) as SourceUser[]; const byDomain = new Map(); for (const row of rows) { if (row.deleted_at) continue; const d = primaryDomain(row); if (!d) continue; if (!byDomain.has(d)) byDomain.set(d, row); for (const extra of parseDomains(row.domains)) { if (!byDomain.has(extra)) byDomain.set(extra, row); } } const tenants = await prisma.tenant.findMany({ where: { product: ProductType.hamkar }, }); const filtered = onlyDomain ? tenants.filter((tenant) => { const keys = [ tenant.domain?.toLowerCase(), ...((Array.isArray(tenant.domains) ? tenant.domains : []) as string[]).map((d) => String(d).toLowerCase(), ), ]; return keys.includes(onlyDomain); }) : tenants; let updated = 0; let restored = 0; let skipped = 0; for (const tenant of filtered) { const domainKeys = [ tenant.domain?.toLowerCase(), ...((Array.isArray(tenant.domains) ? tenant.domains : []) as string[]).map((d) => String(d).toLowerCase(), ), ].filter(Boolean) as string[]; const source = domainKeys.map((d) => byDomain.get(d)).find(Boolean) || null; const noteMode = tenant.notes?.match(/registryMode:([^\s|]+)/)?.[1] || tenant.notes?.match(/(?:^|\|)\s*mode:([^\s|]+)/)?.[1] || null; const registryMode = (source?.mode && String(source.mode).trim()) || (source?.theme && String(source.theme).trim()) || noteMode || 'dynamicMode'; const brandPrimary = emptyToNull(source?.color) ?? emptyToNull(tenant.primaryColor); const brandSecondary = emptyToNull(source?.secondary_color) ?? emptyToNull(tenant.secondaryColor); const brandBackground = emptyToNull(source?.background_color) ?? emptyToNull(tenant.backgroundColor); const existing = tenant.appUiConfig && typeof tenant.appUiConfig === 'object' && (tenant.appUiConfig as any).kind === 'hamkar' ? (tenant.appUiConfig as Record) : null; const defaultSnapshot = { registryMode, brand: { primaryColor: brandPrimary, secondaryColor: brandSecondary, backgroundColor: brandBackground, }, settings: { pricing_box_theme: String(existing?.settings?.pricing_box_theme || '1'), price_font_size: String(existing?.settings?.price_font_size || 'بزرگ'), orderUI: String(existing?.settings?.orderUI || 'old'), }, }; // Never overwrite an existing defaultSnapshot (immutable factory look) const keepSnapshot = existing?.defaultSnapshot && typeof existing.defaultSnapshot === 'object' ? existing.defaultSnapshot : defaultSnapshot; const nextUi = buildHamkarUiFromBrand({ primaryColor: restore ? keepSnapshot.brand?.primaryColor : existing?.brand?.primaryColor ?? brandPrimary, secondaryColor: restore ? keepSnapshot.brand?.secondaryColor : existing?.brand?.secondaryColor ?? brandSecondary, backgroundColor: restore ? keepSnapshot.brand?.backgroundColor : existing?.brand?.backgroundColor ?? brandBackground, menuIconColor: restore ? keepSnapshot.brand?.menuIconColor : existing?.brand?.menuIconColor ?? null, menuIconActiveColor: restore ? keepSnapshot.brand?.menuIconActiveColor : existing?.brand?.menuIconActiveColor ?? null, settingsManaged: Boolean(existing?.settingsManaged), brandManaged: Boolean(existing?.brandManaged), registryMode: restore ? keepSnapshot.registryMode : existing?.registryMode || registryMode, settings: restore ? keepSnapshot.settings : existing?.settings, }); const appUiConfig = { ...nextUi, defaultSnapshot: keepSnapshot, settings: nextUi.settings, }; const unchanged = JSON.stringify(existing) === JSON.stringify(appUiConfig) && !restore; if (unchanged) { skipped += 1; continue; } await prisma.tenant.update({ where: { id: tenant.id }, data: { appUiConfig: appUiConfig as any, ...(restore ? { primaryColor: keepSnapshot.brand?.primaryColor ?? null, secondaryColor: keepSnapshot.brand?.secondaryColor ?? null, backgroundColor: keepSnapshot.brand?.backgroundColor ?? null, } : {}), }, }); updated += 1; if (restore) restored += 1; console.log( `${restore ? 'restored' : 'seeded'}: ${tenant.domain} → default=${keepSnapshot.registryMode}`, ); } console.log({ tenants: filtered.length, updated, restored, skipped, restore, domain: onlyDomain, }); } main() .catch((err) => { console.error(err); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });