143 lines
3.7 KiB
TypeScript
143 lines
3.7 KiB
TypeScript
/**
|
|
* 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: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
SMS_IMPORT_DOMAIN: target.domain,
|
|
SMS_IMPORT_PRODUCT: product,
|
|
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),
|
|
};
|
|
if (dryRun) {
|
|
childEnv.SMS_IMPORT_DRY_RUN = '1';
|
|
} else {
|
|
delete childEnv.SMS_IMPORT_DRY_RUN;
|
|
}
|
|
|
|
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();
|