144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
/**
|
||
* One-off: attach missing business owners from contactPhone.
|
||
* Usage: node --import ts-node/register/esm OR npx ts-node -r dotenv/config scripts/backfill-business-owners.ts
|
||
*/
|
||
import 'dotenv/config';
|
||
import { DataSource } from 'typeorm';
|
||
import { join } from 'path';
|
||
|
||
const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹';
|
||
const ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩';
|
||
const INVISIBLE_CHARS = /[\u200c\u200d\u202a-\u202e\ufeff]/g;
|
||
const IRAN_MOBILE_REGEX = /^09\d{9}$/;
|
||
|
||
function toAsciiDigits(value: string): string {
|
||
return value.replace(/[۰-۹٠-٩]/g, (ch) => {
|
||
const persian = PERSIAN_DIGITS.indexOf(ch);
|
||
if (persian >= 0) return String(persian);
|
||
const arabic = ARABIC_DIGITS.indexOf(ch);
|
||
if (arabic >= 0) return String(arabic);
|
||
return ch;
|
||
});
|
||
}
|
||
|
||
function normalizeIranMobile(raw: string): string {
|
||
let mobile = toAsciiDigits(String(raw ?? '').trim());
|
||
mobile = mobile.replace(INVISIBLE_CHARS, '');
|
||
let digits = mobile.replace(/\D/g, '');
|
||
if (!digits) return '';
|
||
if (digits.startsWith('0098')) digits = digits.slice(2);
|
||
if (digits.startsWith('98') && digits.length >= 12) {
|
||
digits = `0${digits.slice(2)}`;
|
||
} else if (digits.startsWith('9') && digits.length === 10) {
|
||
digits = `0${digits}`;
|
||
}
|
||
return digits;
|
||
}
|
||
|
||
function isValidIranMobile(mobile: string): boolean {
|
||
return IRAN_MOBILE_REGEX.test(mobile);
|
||
}
|
||
|
||
async function main() {
|
||
const ds = new DataSource({
|
||
type: 'postgres',
|
||
host: process.env.DB_HOST,
|
||
port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
|
||
username: process.env.POSTGRES_USER,
|
||
password: process.env.POSTGRES_PASSWORD,
|
||
database: process.env.POSTGRES_DB,
|
||
entities: [],
|
||
synchronize: false,
|
||
});
|
||
|
||
await ds.initialize();
|
||
console.log('Connected to', process.env.POSTGRES_DB);
|
||
|
||
const businesses: Array<{
|
||
id: string;
|
||
title: string;
|
||
contactPhone: string | null;
|
||
ownerId: string | null;
|
||
}> = await ds.query(`
|
||
SELECT b.id, b.title, b."contactPhone", b."ownerId"
|
||
FROM businesses b
|
||
LEFT JOIN users u ON u.id = b."ownerId"
|
||
WHERE b."contactPhone" IS NOT NULL
|
||
AND b."contactPhone" <> ''
|
||
AND (b."ownerId" IS NULL OR u.id IS NULL)
|
||
ORDER BY b."createdAt" ASC
|
||
`);
|
||
|
||
console.log(`Found ${businesses.length} business(es) needing owner backfill`);
|
||
|
||
let created = 0;
|
||
let linked = 0;
|
||
let skipped = 0;
|
||
|
||
for (const biz of businesses) {
|
||
const mobile = normalizeIranMobile(biz.contactPhone ?? '');
|
||
if (!isValidIranMobile(mobile)) {
|
||
console.log(`SKIP ${biz.title}: invalid mobile "${biz.contactPhone}"`);
|
||
skipped += 1;
|
||
continue;
|
||
}
|
||
|
||
const defaultName = `مدیر ${biz.title}`;
|
||
|
||
let users: Array<{ id: string; fullName: string | null; role: string }> =
|
||
await ds.query(`SELECT id, "fullName", role FROM users WHERE mobile = $1 LIMIT 1`, [
|
||
mobile,
|
||
]);
|
||
|
||
let userId: string;
|
||
if (!users.length) {
|
||
const inserted: Array<{ id: string }> = await ds.query(
|
||
`
|
||
INSERT INTO users (id, mobile, "fullName", role, "isActive", "createdAt", "updatedAt")
|
||
VALUES (gen_random_uuid(), $1, $2, 'business_owner', true, NOW(), NOW())
|
||
RETURNING id
|
||
`,
|
||
[mobile, defaultName],
|
||
);
|
||
userId = inserted[0].id;
|
||
created += 1;
|
||
console.log(`CREATE user ${mobile} (${defaultName}) → ${biz.title}`);
|
||
} else {
|
||
userId = users[0].id;
|
||
const updates: string[] = [];
|
||
const params: unknown[] = [];
|
||
|
||
if (users[0].role !== 'admin') {
|
||
params.push('business_owner');
|
||
updates.push(`role = $${params.length}`);
|
||
}
|
||
if (!users[0].fullName?.trim()) {
|
||
params.push(defaultName);
|
||
updates.push(`"fullName" = $${params.length}`);
|
||
}
|
||
params.push(true);
|
||
updates.push(`"isActive" = $${params.length}`);
|
||
params.push(userId);
|
||
await ds.query(
|
||
`UPDATE users SET ${updates.join(', ')}, "updatedAt" = NOW() WHERE id = $${params.length}`,
|
||
params,
|
||
);
|
||
linked += 1;
|
||
console.log(`LINK existing user ${mobile} → ${biz.title}`);
|
||
}
|
||
|
||
await ds.query(
|
||
`UPDATE businesses SET "ownerId" = $1, "contactPhone" = $2, "updatedAt" = NOW() WHERE id = $3`,
|
||
[userId, mobile, biz.id],
|
||
);
|
||
}
|
||
|
||
console.log(`Done. created=${created} linked=${linked} skipped=${skipped}`);
|
||
await ds.destroy();
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error(err);
|
||
process.exit(1);
|
||
});
|