From dd4850d9fadc1a956b475a11bcde15f678d30c30 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Tue, 11 Aug 2026 17:53:04 +0330 Subject: [PATCH] fix: lock new.gholaktala.ir as Khanegi newTheme (userTheme) Correct prior modular/Hamkar misclassification. Protect locked newTheme domains from bulk-modular brand overrides. Co-authored-by: Cursor --- prisma/bulk-khanegi-modular-brand.ts | 36 ++---- prisma/import-users.ts | 2 + prisma/khanegi-variant-locks.ts | 53 ++++++++ .../fix-gholaktala-to-khanegi-newtheme.mjs | 113 ++++++++++++++++++ 4 files changed, 181 insertions(+), 23 deletions(-) create mode 100644 prisma/khanegi-variant-locks.ts create mode 100644 scripts/fix-gholaktala-to-khanegi-newtheme.mjs diff --git a/prisma/bulk-khanegi-modular-brand.ts b/prisma/bulk-khanegi-modular-brand.ts index 00a2a90..2f1be0c 100644 --- a/prisma/bulk-khanegi-modular-brand.ts +++ b/prisma/bulk-khanegi-modular-brand.ts @@ -1,13 +1,13 @@ /** - * Apply Niazi-style modular brand colors to all Khanegi tenants - * that are NOT newTheme (Aygin stays newTheme). + * Apply Niazi-style modular brand colors to Khanegi tenants + * that are NOT locked newTheme (Aygin, new.gholaktala.ir, …). * * Colors (from app.niazitala.ir): * primary #0a1b37 (هدر) * secondary #e0be7f (اکسنت) * background #ffffff (پس‌زمینه) * - * Also forces uiVariant=modular for non-Aygin Khanegi rows. + * Also forces uiVariant=modular for non-protected Khanegi rows. * * Dry-run: * KHANEGI_BRAND_DRY_RUN=1 npm run prisma:bulk-khanegi-modular-brand @@ -19,6 +19,7 @@ * KHANEGI_BRAND_SOURCE_DOMAIN=app.niazitala.ir npm run prisma:bulk-khanegi-modular-brand */ import { KhanegiUiVariant, PrismaClient, ProductType } from '@prisma/client'; +import { isForcedKhanegiNewTheme } from './khanegi-variant-locks'; const prisma = new PrismaClient(); @@ -42,20 +43,9 @@ function normalizeHost(value?: string | null) { .split(':')[0]; } -function isAyginTenant(domain: string, domains: unknown, slug: string) { - const hosts = new Set(); - const primary = normalizeHost(domain); - if (primary) hosts.add(primary); - const extra = Array.isArray(domains) ? domains : []; - for (const d of extra) { - const n = normalizeHost(String(d)); - if (n) hosts.add(n); - } - if (String(slug || '').toLowerCase().includes('aygin')) return true; - for (const h of hosts) { - if (h.includes('aygingold') || h === 'app.aygingold.ir') return true; - } - return false; +function isProtectedNewThemeTenant(domain: string, domains: unknown, slug: string) { + // Aygin + explicit locks (e.g. new.gholaktala.ir) + return isForcedKhanegiNewTheme(domain, domains, slug); } function bumpAppVersion(current?: string | null): string { @@ -134,10 +124,10 @@ async function main() { }); const targets = tenants.filter( - (t) => !isAyginTenant(t.domain, t.domains, t.slug), + (t) => !isProtectedNewThemeTenant(t.domain, t.domains, t.slug), ); - const skippedAygin = tenants.filter((t) => - isAyginTenant(t.domain, t.domains, t.slug), + const skippedNewTheme = tenants.filter((t) => + isProtectedNewThemeTenant(t.domain, t.domains, t.slug), ); console.log( @@ -153,7 +143,7 @@ async function main() { }, totalKhanegi: tenants.length, targets: targets.length, - skippedAygin: skippedAygin.map((t) => ({ + skippedNewTheme: skippedNewTheme.map((t) => ({ domain: t.domain, uiVariant: t.uiVariant, })), @@ -204,8 +194,8 @@ async function main() { updated += 1; } - // Ensure Aygin stays newTheme (do not touch colors unless variant wrong) - for (const t of skippedAygin) { + // Ensure locked newTheme tenants stay newTheme + for (const t of skippedNewTheme) { if (t.uiVariant === KhanegiUiVariant.newTheme) continue; console.log( JSON.stringify({ diff --git a/prisma/import-users.ts b/prisma/import-users.ts index be26cf6..5051c3f 100644 --- a/prisma/import-users.ts +++ b/prisma/import-users.ts @@ -30,6 +30,8 @@ const FORCE_KHANEGI: Array<{ domain?: string; name?: string; externalId?: number { domain: 'abaeigold.ir' }, { domain: 'niazitala.ir' }, { domain: 'garoosigholaktala.ir' }, + // Khanegi newTheme (userTheme front) — NOT modular, NOT Hamkar. + // Keep uiVariant=newTheme in DB; see prisma/khanegi-variant-locks.ts { domain: 'new.gholaktala.ir' }, { domain: 'kh.kashkolgold.ir' }, { domain: 'goldsun.zaarbox.ir' }, diff --git a/prisma/khanegi-variant-locks.ts b/prisma/khanegi-variant-locks.ts new file mode 100644 index 0000000..26645b3 --- /dev/null +++ b/prisma/khanegi-variant-locks.ts @@ -0,0 +1,53 @@ +/** + * Khanegi tenants that MUST stay on userTheme (uiVariant=newTheme). + * Never force these to modular in bulk brand scripts. + * + * Frontend: KhanegiModular/userTheme + * API: /api/get_settings (NOT Hamkar /site/settings, NOT userModular-only assumptions) + */ +export const FORCE_KHANEGI_NEW_THEME_DOMAINS = [ + 'new.gholaktala.ir', + 'app.aygingold.ir', +] as const + +export function normalizeTenantHost(value?: string | null) { + if (!value) return '' + return String(value) + .trim() + .toLowerCase() + .replace(/^https?:\/\//, '') + .split('/')[0] + .split(':')[0] +} + +export function collectTenantHosts(domain: string, domains: unknown, slug = '') { + const hosts = new Set() + const primary = normalizeTenantHost(domain) + if (primary) hosts.add(primary) + const extra = Array.isArray(domains) ? domains : [] + for (const d of extra) { + const n = normalizeTenantHost(String(d)) + if (n) hosts.add(n) + } + const s = String(slug || '').toLowerCase() + if (s) hosts.add(s) + return hosts +} + +export function isForcedKhanegiNewTheme(domain: string, domains: unknown, slug = '') { + const hosts = collectTenantHosts(domain, domains, slug) + const slugLower = String(slug || '').toLowerCase() + if (slugLower.includes('aygin') || slugLower.includes('gholak_new')) return true + + for (const h of hosts) { + if (h === 'new.gholaktala.ir') return true + if (h.includes('aygingold') || h === 'app.aygingold.ir') return true + } + + for (const locked of FORCE_KHANEGI_NEW_THEME_DOMAINS) { + const needle = normalizeTenantHost(locked) + if (hosts.has(needle)) return true + } + + return false +} diff --git a/scripts/fix-gholaktala-to-khanegi-newtheme.mjs b/scripts/fix-gholaktala-to-khanegi-newtheme.mjs new file mode 100644 index 0000000..2dfad7e --- /dev/null +++ b/scripts/fix-gholaktala-to-khanegi-newtheme.mjs @@ -0,0 +1,113 @@ +import { PrismaClient } from '@prisma/client' + +/** + * Restore new.gholaktala.ir as Khanegi + newTheme (front: userTheme). + * + * On production: + * cd /var/www/superadmin/api && git pull + * set -a && . ./.env && set +a + * node scripts/fix-gholaktala-to-khanegi-newtheme.mjs + * node scripts/fix-gholaktala-to-khanegi-newtheme.mjs --apply + * + * Then verify: + * curl -s 'https://adm.montzar.ir/get_user?domain=new.gholaktala.ir' | head -c 500 + * Expect: "product":"khanegi" and "ui_variant":"newTheme" + */ +const prisma = new PrismaClient() +const APPLY = process.argv.includes('--apply') +const DOMAIN = 'new.gholaktala.ir' + +function bumpAppVersion(current) { + const v = String(current || '1').trim() || '1' + if (/^\d+$/.test(v)) return String(Number(v) + 1) + const semver = v.match(/^(\d+)\.(\d+)\.(\d+)$/) + if (semver) return `${semver[1]}.${semver[2]}.${Number(semver[3]) + 1}` + return String(Date.now()) +} + +async function main() { + const tenants = await prisma.tenant.findMany({ + where: { + OR: [ + { domain: { equals: DOMAIN, mode: 'insensitive' } }, + { domains: { string_contains: DOMAIN } }, + { domain: { contains: 'gholaktala', mode: 'insensitive' } }, + { slug: { contains: 'gholak', mode: 'insensitive' } }, + ], + }, + select: { + id: true, + nameFa: true, + domain: true, + slug: true, + product: true, + uiVariant: true, + appVersion: true, + notes: true, + }, + }) + + console.log('MATCHES', JSON.stringify(tenants, null, 2)) + + const tenant = + tenants.find((t) => String(t.domain).toLowerCase() === DOMAIN) || tenants[0] + + if (!tenant) throw new Error(`Tenant not found for ${DOMAIN}`) + + const alreadyOk = tenant.product === 'khanegi' && tenant.uiVariant === 'newTheme' + + console.log('SELECTED', { + id: tenant.id, + domain: tenant.domain, + nameFa: tenant.nameFa, + product: tenant.product, + uiVariant: tenant.uiVariant, + alreadyOk, + expected: { + product: 'khanegi', + ui_variant: 'newTheme', + frontend: 'KhanegiModular/userTheme (NOT userModular, NOT Hamkar)', + api: '/server/api/get_settings', + }, + }) + + if (!APPLY) { + console.log('DRY_RUN — re-run with --apply to set product=khanegi uiVariant=newTheme') + return + } + + const updated = await prisma.tenant.update({ + where: { id: tenant.id }, + data: { + product: 'khanegi', + uiVariant: 'newTheme', + appVersion: alreadyOk ? tenant.appVersion : bumpAppVersion(tenant.appVersion), + notes: [ + String(tenant.notes || '') + .replace(/\s*\|\s*product=hamkar[^\|]*/gi, '') + .replace(/\s*\|\s*product=khanegi[^\|]*/gi, '') + .trim(), + 'product=khanegi uiVariant=newTheme (userTheme locked)', + ] + .filter(Boolean) + .join(' | '), + }, + }) + + console.log('UPDATED', { + id: updated.id, + domain: updated.domain, + product: updated.product, + uiVariant: updated.uiVariant, + appVersion: updated.appVersion, + }) +} + +main() + .catch((e) => { + console.error(e) + process.exitCode = 1 + }) + .finally(async () => { + await prisma.$disconnect() + })