/** * Import JSON exported from production MySQL (VM) into local Postgres. * * Usage: * npx ts-node -r tsconfig-paths/register scripts/import-legacy-export.ts * * Optional: * --dir scripts/data * --replace wipe products/blogs/representations (and non-kept users) before import */ import { promises as fs } from 'fs' import * as path from 'path' import { PrismaClient, RepresentationStatus, UserRole, } from '@prisma/client' const prisma = new PrismaClient() function arg(name: string, fallback?: string): string | undefined { const idx = process.argv.indexOf(`--${name}`) if (idx >= 0 && process.argv[idx + 1] && !process.argv[idx + 1].startsWith('--')) { return process.argv[idx + 1] } if (process.argv.includes(`--${name}`)) return 'true' return fallback } function legacyId(prefix: string, id: string | number): string { return `legacy-${prefix}-${id}` } function normalizePhone(raw: string | null | undefined): string { const digits = String(raw ?? '').replace(/\D/g, '') if (digits.length === 10 && digits.startsWith('9')) return `0${digits}` if (digits.length === 11 && digits.startsWith('09')) return digits return digits || '09000000000' } function mapRole(role: string | null | undefined, cellNumber: string): UserRole { if (cellNumber === '09127004945') return UserRole.superAdmin switch (role) { case 'Admin': return UserRole.Admin case 'Agent': return UserRole.Agent default: return UserRole.User } } function mapRequestStatus(status: number): RepresentationStatus { if (status === 2) return RepresentationStatus.active if (status === 1) return RepresentationStatus.rejected return RepresentationStatus.pending } function toDate(value: string | null | undefined): Date | undefined { if (!value) return undefined const d = new Date(value) return Number.isNaN(d.getTime()) ? undefined : d } function phpBcryptToNode(hash: string): string { // Node bcrypt accepts $2y$ in most versions; normalize to $2b$ just in case. if (hash.startsWith('$2y$')) return `$2b$${hash.slice(4)}` return hash } async function readJson(file: string): Promise { const raw = await fs.readFile(file, 'utf8') return JSON.parse(raw) as T } async function main() { const dir = path.resolve( process.cwd(), arg('dir', 'scripts/data') ?? 'scripts/data', ) const replace = Boolean(arg('replace')) type UserRow = { id: string first_name: string last_name: string mobile: string password: string role: string created_at?: string updated_at?: string } type ProductRow = { id: string title_fa: string title_en: string price: string quantity: string category: string description: string image_url: string | null created_at?: string updated_at?: string } type BlogRow = { id: string title: string summary: string body: string author: string published: boolean image_url: string | null created_at?: string updated_at?: string } type AgentRow = { id: string name: string owner: string national_code: string mobile: string phone: string postal_code: string address: string description: string city: string province: string created_at?: string updated_at?: string } type RequestRow = { id: string first_name: string last_name: string national_code: string mobile: string phone: string address: string postal_code: string description: string status: number city: string province: string created_at?: string updated_at?: string } const users = await readJson(path.join(dir, 'users.json')) const products = await readJson(path.join(dir, 'products.json')) const blogs = await readJson(path.join(dir, 'blogs.json')) const agents = await readJson(path.join(dir, 'agents.json')) const requests = await readJson( path.join(dir, 'agent_requests.json'), ) if (replace) { console.log('Clearing existing catalog / representation / non-superAdmin users…') await prisma.orderItem.deleteMany({}) await prisma.order.deleteMany({}) await prisma.review.deleteMany({}) await prisma.callRequest.deleteMany({}) await prisma.representation.deleteMany({}) await prisma.blogPost.deleteMany({}) await prisma.product.deleteMany({}) await prisma.refreshToken.deleteMany({}) await prisma.smsOtp.deleteMany({}) await prisma.user.deleteMany({ where: { role: { not: UserRole.superAdmin } }, }) } let usersUpserted = 0 for (const u of users) { const cellNumber = normalizePhone(u.mobile) const id = legacyId('user', u.id) const role = mapRole(u.role, cellNumber) const passwordHash = phpBcryptToNode(u.password) const data = { firstName: (u.first_name || '—').slice(0, 190), lastName: (u.last_name || '—').slice(0, 190), cellNumber, passwordHash, role, disabled: false, createdAt: toDate(u.created_at), updatedAt: toDate(u.updated_at), } const byPhone = await prisma.user.findUnique({ where: { cellNumber } }) if (byPhone) { await prisma.user.update({ where: { id: byPhone.id }, data: { firstName: data.firstName, lastName: data.lastName, passwordHash: data.passwordHash, // keep local superAdmin elevation role: byPhone.role === UserRole.superAdmin || role === UserRole.superAdmin ? UserRole.superAdmin : role, }, }) } else { await prisma.user.upsert({ where: { id }, create: { id, ...data }, update: { firstName: data.firstName, lastName: data.lastName, cellNumber: data.cellNumber, passwordHash: data.passwordHash, role: data.role, }, }) } usersUpserted += 1 } let productsUpserted = 0 for (const p of products) { const id = legacyId('product', p.id) const price = Number.parseInt(String(p.price || '0'), 10) || 0 const stock = Number.parseInt(String(p.quantity || '0'), 10) || 0 await prisma.product.upsert({ where: { id }, create: { id, nameFa: p.title_fa || 'بدون عنوان', nameEn: p.title_en || '', price, stock, category: p.category || '', imageUrl: p.image_url || null, createdAt: toDate(p.created_at), updatedAt: toDate(p.updated_at), }, update: { nameFa: p.title_fa || 'بدون عنوان', nameEn: p.title_en || '', price, stock, category: p.category || '', imageUrl: p.image_url || null, }, }) productsUpserted += 1 } let blogsUpserted = 0 for (const b of blogs) { const id = legacyId('blog', b.id) await prisma.blogPost.upsert({ where: { id }, create: { id, title: b.title || 'بدون عنوان', summary: b.summary || '', body: b.body || '', author: (b.author || '').trim(), imageUrl: b.image_url || null, published: Boolean(b.published), createdAt: toDate(b.created_at), updatedAt: toDate(b.updated_at), }, update: { title: b.title || 'بدون عنوان', summary: b.summary || '', body: b.body || '', author: (b.author || '').trim(), imageUrl: b.image_url || null, published: Boolean(b.published), }, }) blogsUpserted += 1 } let repsUpserted = 0 for (const a of agents) { const id = legacyId('agent', a.id) const mobile = normalizePhone(a.mobile) const owner = a.owner || a.name || '' const nameParts = owner.trim().split(/\s+/) await prisma.representation.upsert({ where: { id }, create: { id, name: a.name || owner || `نمایندگی ${a.id}`, owner, firstName: nameParts[0] || '', lastName: nameParts.slice(1).join(' '), nationalCode: a.national_code || '', mobile, province: a.province || '', city: a.city || '', landline: a.phone || '', address: a.address || '', postalCode: a.postal_code || '', description: a.description || '', status: RepresentationStatus.active, createdAt: toDate(a.created_at), updatedAt: toDate(a.updated_at), }, update: { name: a.name || owner || `نمایندگی ${a.id}`, owner, nationalCode: a.national_code || '', mobile, province: a.province || '', city: a.city || '', landline: a.phone || '', address: a.address || '', postalCode: a.postal_code || '', description: a.description || '', status: RepresentationStatus.active, }, }) repsUpserted += 1 } let requestsUpserted = 0 for (const r of requests) { const id = legacyId('agent-request', r.id) const mobile = normalizePhone(r.mobile) const status = mapRequestStatus(Number(r.status) || 0) const name = `${r.first_name || ''} ${r.last_name || ''}`.trim() await prisma.representation.upsert({ where: { id }, create: { id, name: name || `درخواست ${r.id}`, owner: name, firstName: r.first_name || '', lastName: r.last_name || '', nationalCode: r.national_code || '', mobile, province: r.province || '', city: r.city || '', landline: r.phone || '', address: r.address || '', postalCode: r.postal_code || '', description: r.description || '', status, createdAt: toDate(r.created_at), updatedAt: toDate(r.updated_at), }, update: { name: name || `درخواست ${r.id}`, owner: name, firstName: r.first_name || '', lastName: r.last_name || '', nationalCode: r.national_code || '', mobile, province: r.province || '', city: r.city || '', landline: r.phone || '', address: r.address || '', postalCode: r.postal_code || '', description: r.description || '', status, }, }) requestsUpserted += 1 } const counts = { users: await prisma.user.count(), products: await prisma.product.count(), blogs: await prisma.blogPost.count(), representations: await prisma.representation.count(), activeReps: await prisma.representation.count({ where: { status: RepresentationStatus.active }, }), pendingReps: await prisma.representation.count({ where: { status: RepresentationStatus.pending }, }), } console.log('Imported:', { usersUpserted, productsUpserted, blogsUpserted, agentsAsActive: repsUpserted, agentRequests: requestsUpserted, }) console.log('DB totals:', counts) } main() .catch((err) => { console.error(err) process.exitCode = 1 }) .finally(async () => { await prisma.$disconnect() })