import { fileURLToPath, pathToFileURL } from 'node:url'; import pg from 'pg'; const DEFAULT_TENANT_ID = '00000000-0000-0000-0000-000000000001'; const LEGACY_SMS_PROVIDERS = [ 'aliyun', 'aliyun-sms', 'aliyun_sms', 'tencent', 'tencent-sms', 'tencent_sms', ]; function envString(env, key, fallback = '') { return typeof env[key] === 'string' && env[key].trim() ? env[key].trim() : fallback; } function buildConfig(env = process.env, argv = process.argv.slice(2)) { const databaseUrl = envString(env, 'DATABASE_URL'); const tenantId = envString(env, 'PNVS_TENANT_ID', envString(env, 'TENANT_ID', DEFAULT_TENANT_ID)); const apply = argv.includes('--apply'); if (!databaseUrl) throw new Error('Missing required env: DATABASE_URL'); if (!tenantId) throw new Error('Missing required env: PNVS_TENANT_ID or TENANT_ID'); return { databaseUrl, tenantId, apply }; } async function findLegacySmsProviderRows(query, tenantId) { const result = await query( ` select id, tenant_id as "tenantId", provider, status, display_name as "displayName", updated_at as "updatedAt" from public.tenant_auth_providers where tenant_id = $1::uuid and lower(provider) = any($2::text[]) and status in ('active', 'testing') order by provider asc `, [tenantId, LEGACY_SMS_PROVIDERS], ); return result.rows || []; } async function disableLegacySmsProviders(inputConfig, options = {}) { const config = inputConfig?.databaseUrl ? inputConfig : buildConfig(options.env || process.env, options.argv || process.argv.slice(2)); const query = options.query; if (query) { const rows = await findLegacySmsProviderRows(query, config.tenantId); return { tenantId: config.tenantId, dryRun: !config.apply, changed: config.apply ? rows.length : 0, rows }; } const pool = new pg.Pool({ connectionString: config.databaseUrl, max: 1 }); try { const rows = await findLegacySmsProviderRows((sql, params) => pool.query(sql, params), config.tenantId); if (!config.apply || rows.length === 0) { return { tenantId: config.tenantId, dryRun: true, changed: 0, rows }; } const ids = rows.map(row => row.id); const result = await pool.query( ` update public.tenant_auth_providers set status = 'disabled', updated_at = now() where id = any($1::uuid[]) returning id, tenant_id as "tenantId", provider, status, display_name as "displayName", updated_at as "updatedAt" `, [ids], ); return { tenantId: config.tenantId, dryRun: false, changed: result.rowCount || 0, rows: result.rows || [] }; } finally { await pool.end(); } } async function main() { try { const result = await disableLegacySmsProviders(); console.log(JSON.stringify({ ok: true, tenantId: result.tenantId, dryRun: result.dryRun, changed: result.changed, rows: result.rows.map(row => ({ id: row.id, provider: row.provider, status: row.status, displayName: row.displayName || '', updatedAt: row.updatedAt || null, })), }, null, 2)); if (result.dryRun && result.rows.length > 0) { console.error('Dry-run only. Re-run with --apply to disable these legacy SMS auth providers.'); } } catch (error) { console.error(error.message); console.error(` Usage: set -a source /etc/tiku-saas/api.env set +a PNVS_TENANT_ID=00000000-0000-0000-0000-000000000001 npm run disable:legacy-sms-providers PNVS_TENANT_ID=00000000-0000-0000-0000-000000000001 npm run disable:legacy-sms-providers -- --apply `); process.exitCode = 1; } } const currentFile = fileURLToPath(import.meta.url); if (process.argv[1] && fileURLToPath(pathToFileURL(process.argv[1])) === currentFile) { await main(); } export { buildConfig, disableLegacySmsProviders, findLegacySmsProviderRows };