/** * Batch-import SMS wallets for many sold versions from a targets JSON file. * * Each target is one Khanegi/Hamkar MySQL (one sold version). Never share one * DB/env across multiple domains on the same host. * * SMS_IMPORT_TARGETS=./prisma/khanegi-sms-targets.example.json \ * SMS_IMPORT_DRY_RUN=1 \ * npm run prisma:import-sms-batch * * Target shape: * [ * { * "domain": "example.ir", * "product": "khanegi", * "pilot": true, * "db": { "host": "127.0.0.1", "port": 3306, "user": "u", "password": "p", "database": "db" } * } * ] */ import * as fs from 'fs'; import * as path from 'path'; import { spawnSync } from 'child_process'; type DbTarget = { host?: string; port?: number | string; user?: string; password?: string; database: string; }; type ImportTarget = { domain: string; product?: 'khanegi' | 'hamkar'; pilot?: boolean; skip?: boolean; db: DbTarget; }; function env(name: string, fallback = '') { return String(process.env[name] ?? fallback).trim(); } function loadTargets(filePath: string): ImportTarget[] { const abs = path.resolve(filePath); if (!fs.existsSync(abs)) { throw new Error(`Targets file not found: ${abs}`); } const raw = JSON.parse(fs.readFileSync(abs, 'utf8')) as ImportTarget[]; if (!Array.isArray(raw)) throw new Error('Targets JSON must be an array'); return raw; } function main() { const targetsPath = env('SMS_IMPORT_TARGETS') || path.join(__dirname, 'khanegi-sms-targets.example.json'); const dryRun = env('SMS_IMPORT_DRY_RUN') === '1'; const onlyPilot = env('SMS_IMPORT_ONLY_PILOT') === '1'; const onlyDomain = env('SMS_IMPORT_DOMAIN'); let targets = loadTargets(targetsPath).filter((t) => !t.skip); if (onlyPilot) targets = targets.filter((t) => t.pilot); if (onlyDomain) { targets = targets.filter( (t) => t.domain.toLowerCase() === onlyDomain.toLowerCase(), ); } if (!targets.length) { console.error('No targets to import.'); process.exitCode = 1; return; } console.log( JSON.stringify( { targetsPath, dryRun, onlyPilot, onlyDomain: onlyDomain || null, count: targets.length, domains: targets.map((t) => t.domain), }, null, 2, ), ); const script = path.join(__dirname, 'import-sms-from-product.ts'); let failed = 0; for (const target of targets) { const product = target.product || 'khanegi'; const db = target.db || ({} as DbTarget); if (!db.database) { console.error(`SKIP ${target.domain}: missing db.database`); failed += 1; continue; } const childEnv = { ...process.env, SMS_IMPORT_DOMAIN: target.domain, SMS_IMPORT_PRODUCT: product, SMS_IMPORT_DRY_RUN: dryRun ? '1' : env('SMS_IMPORT_DRY_RUN'), SMS_IMPORT_DB_HOST: String(db.host || '127.0.0.1'), SMS_IMPORT_DB_PORT: String(db.port || 3306), SMS_IMPORT_DB_USER: String(db.user || 'root'), SMS_IMPORT_DB_PASSWORD: String(db.password ?? ''), SMS_IMPORT_DB_DATABASE: String(db.database), }; console.log(`\n=== ${dryRun ? 'DRY ' : ''}IMPORT ${target.domain} (${product}) ===`); const result = spawnSync( process.platform === 'win32' ? 'npx.cmd' : 'npx', ['ts-node', '--transpile-only', script], { env: childEnv, cwd: path.join(__dirname, '..'), stdio: 'inherit', shell: process.platform === 'win32', }, ); if (result.status !== 0) { failed += 1; console.error(`FAILED: ${target.domain} (exit ${result.status})`); } } console.log(`\nDone. failed=${failed} / total=${targets.length}`); if (failed) process.exitCode = 1; } main();