Files
backend/scripts/import-legacy-export.ts
T

397 lines
11 KiB
TypeScript

/**
* 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<T>(file: string): Promise<T> {
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<UserRow[]>(path.join(dir, 'users.json'))
const products = await readJson<ProductRow[]>(path.join(dir, 'products.json'))
const blogs = await readJson<BlogRow[]>(path.join(dir, 'blogs.json'))
const agents = await readJson<AgentRow[]>(path.join(dir, 'agents.json'))
const requests = await readJson<RequestRow[]>(
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()
})