feat: refactor Prisma client initialization and update related services

This commit is contained in:
2026-06-16 15:47:29 +03:30
parent 652177862d
commit f87e5b9d8e
8 changed files with 192 additions and 200 deletions
+4 -1
View File
@@ -1,7 +1,10 @@
import { PasswordUtil } from '@/common/utils/password.util' import { PasswordUtil } from '@/common/utils/password.util'
import { GoodPricingModel } from '@/generated/prisma/enums' import { GoodPricingModel } from '@/generated/prisma/enums'
import { GoodCreateManyInput } from '@/generated/prisma/models' import { GoodCreateManyInput } from '@/generated/prisma/models'
import { prisma } from '../src/lib/prisma' import { PrismaClient } from '../src/generated/prisma/client'
import { prismaAdapter } from '../src/lib/prisma'
const prisma = new PrismaClient({ adapter: prismaAdapter })
async function main() { async function main() {
const password = await PasswordUtil.hash('123456') const password = await PasswordUtil.hash('123456')
+55 -56
View File
@@ -1,66 +1,65 @@
#!/usr/bin/env ts-node // #!/usr/bin/env ts-node
import * as fs from 'fs' // import * as fs from 'fs'
import * as path from 'path' // import * as path from 'path'
import { prisma } from '../src/lib/prisma'
function findModules(dir: string): string[] { // function findModules(dir: string): string[] {
const results: string[] = [] // const results: string[] = []
const entries = fs.readdirSync(dir, { withFileTypes: true }) // const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const e of entries) { // for (const e of entries) {
const full = path.join(dir, e.name) // const full = path.join(dir, e.name)
if (e.isDirectory()) { // if (e.isDirectory()) {
results.push(...findModules(full)) // results.push(...findModules(full))
} else if (e.isFile() && e.name.endsWith('.module.ts')) { // } else if (e.isFile() && e.name.endsWith('.module.ts')) {
results.push(full) // results.push(full)
} // }
} // }
return results // return results
} // }
function moduleNameFromFile(filePath: string) { // function moduleNameFromFile(filePath: string) {
const name = path.basename(filePath).replace('.module.ts', '') // const name = path.basename(filePath).replace('.module.ts', '')
return name.replace(/-module$|\.module$/i, '') // return name.replace(/-module$|\.module$/i, '')
} // }
function makePermissionsFor(moduleName: string) { // function makePermissionsFor(moduleName: string) {
const base = moduleName.replace(/\W+/g, '_').toLowerCase() // const base = moduleName.replace(/\W+/g, '_').toLowerCase()
return [`${base}:create`, `${base}:read`, `${base}:update`, `${base}:delete`] // return [`${base}:create`, `${base}:read`, `${base}:update`, `${base}:delete`]
} // }
async function main() { // async function main() {
// const srcDir = path.resolve(__dirname, '..', 'src') // // const srcDir = path.resolve(__dirname, '..', 'src')
// const moduleFiles = findModules(srcDir) // // const moduleFiles = findModules(srcDir)
// const modules = moduleFiles.map(moduleNameFromFile) // // const modules = moduleFiles.map(moduleNameFromFile)
// const permsMap: Record<string, boolean> = {} // // const permsMap: Record<string, boolean> = {}
// for (const m of modules) { // // for (const m of modules) {
// for (const p of makePermissionsFor(m)) permsMap[p] = false // // for (const p of makePermissionsFor(m)) permsMap[p] = false
// } // // }
// // Upsert roles: ensure admin has full permissions, others get entries added // // // Upsert roles: ensure admin has full permissions, others get entries added
// const roles = await prisma.role.findMany() // // const roles = await prisma.role.findMany()
// for (const r of roles) { // // for (const r of roles) {
// const current: Record<string, any> = (r.permissions as any) || {} // // const current: Record<string, any> = (r.permissions as any) || {}
// const merged = { ...permsMap, ...current } // // const merged = { ...permsMap, ...current }
// // If role name is admin (case-insensitive), set all permissions to true // // // If role name is admin (case-insensitive), set all permissions to true
// if (r.name && r.name.toLowerCase() === 'admin') { // // if (r.name && r.name.toLowerCase() === 'admin') {
// for (const key of Object.keys(merged)) merged[key] = true // // for (const key of Object.keys(merged)) merged[key] = true
// } else { // // } else {
// // keep existing truthy values, otherwise false // // // keep existing truthy values, otherwise false
// for (const key of Object.keys(merged)) merged[key] = merged[key] || false // // for (const key of Object.keys(merged)) merged[key] = merged[key] || false
// } // // }
// await prisma.role.update({ where: { id: r.id }, data: { permissions: merged } }) // // await prisma.role.update({ where: { id: r.id }, data: { permissions: merged } })
// console.log( // // console.log(
// `Updated role ${r.name} (id=${r.id}) with ${Object.keys(merged).length} permissions`, // // `Updated role ${r.name} (id=${r.id}) with ${Object.keys(merged).length} permissions`,
// ) // // )
// } // // }
await prisma.$disconnect() // await prisma.$disconnect()
} // }
main().catch(e => { // main().catch(e => {
console.error(e) // console.error(e)
process.exit(1) // process.exit(1)
}) // })
+121 -122
View File
@@ -1,144 +1,143 @@
import fs from 'node:fs' // import fs from 'node:fs'
import path from 'node:path' // import path from 'node:path'
import { prisma } from '../src/lib/prisma'
type CsvRow = { // type CsvRow = {
ID?: string // ID?: string
DescriptionOfID?: string // DescriptionOfID?: string
Vat?: string // Vat?: string
Type?: string // Type?: string
} // }
function parseCsvLine(line: string): string[] { // function parseCsvLine(line: string): string[] {
const result: string[] = [] // const result: string[] = []
let current = '' // let current = ''
let inQuotes = false // let inQuotes = false
for (let index = 0; index < line.length; index++) { // for (let index = 0; index < line.length; index++) {
const char = line[index] // const char = line[index]
if (char === '"') { // if (char === '"') {
if (inQuotes && line[index + 1] === '"') { // if (inQuotes && line[index + 1] === '"') {
current += '"' // current += '"'
index++ // index++
} else { // } else {
inQuotes = !inQuotes // inQuotes = !inQuotes
} // }
continue // continue
} // }
if (char === ',' && !inQuotes) { // if (char === ',' && !inQuotes) {
result.push(current) // result.push(current)
current = '' // current = ''
continue // continue
} // }
current += char // current += char
} // }
result.push(current) // result.push(current)
return result.map(value => value.trim()) // return result.map(value => value.trim())
} // }
function parseCsv(content: string): CsvRow[] { // function parseCsv(content: string): CsvRow[] {
const lines = content // const lines = content
.split(/\r?\n/) // .split(/\r?\n/)
.map(line => line.trim()) // .map(line => line.trim())
.filter(Boolean) // .filter(Boolean)
if (!lines.length) return [] // if (!lines.length) return []
const headers = parseCsvLine(lines[0]) // const headers = parseCsvLine(lines[0])
return lines.slice(1).map(line => { // return lines.slice(1).map(line => {
const cols = parseCsvLine(line) // const cols = parseCsvLine(line)
const row: Record<string, string> = {} // const row: Record<string, string> = {}
for (let index = 0; index < headers.length; index++) { // for (let index = 0; index < headers.length; index++) {
row[headers[index]] = cols[index] || '' // row[headers[index]] = cols[index] || ''
} // }
return row // return row
}) // })
} // }
function toBooleanFlags(typeValue: string) { // function toBooleanFlags(typeValue: string) {
const normalized = typeValue || '' // const normalized = typeValue || ''
const isPublic = normalized.includes('شناسه عمومی') // const isPublic = normalized.includes('شناسه عمومی')
const isImported = normalized.includes('وارداتی') // const isImported = normalized.includes('وارداتی')
const isDomestic = !isImported // const isDomestic = !isImported
return { isPublic, isDomestic } // return { isPublic, isDomestic }
} // }
function parseVat(vatValue: string) { // function parseVat(vatValue: string) {
const parsed = Number(vatValue || '0') // const parsed = Number(vatValue || '0')
if (!Number.isFinite(parsed)) return 0 // if (!Number.isFinite(parsed)) return 0
return parsed // return parsed
} // }
async function main() { // async function main() {
const defaultPath = // const defaultPath =
'/Users/ahasani/Desktop/product_good_2026-05-01T11-35-57_part_1_28812448-0dd9-411c-99e1-334c28d781fe.csv' // '/Users/ahasani/Desktop/product_good_2026-05-01T11-35-57_part_1_28812448-0dd9-411c-99e1-334c28d781fe.csv'
const csvPath = process.argv[2] || defaultPath // const csvPath = process.argv[2] || defaultPath
const absolutePath = path.resolve(csvPath) // const absolutePath = path.resolve(csvPath)
if (!fs.existsSync(absolutePath)) { // if (!fs.existsSync(absolutePath)) {
throw new Error(`CSV file not found: ${absolutePath}`) // throw new Error(`CSV file not found: ${absolutePath}`)
} // }
const raw = fs.readFileSync(absolutePath, 'utf8').replace(/^\uFEFF/, '') // const raw = fs.readFileSync(absolutePath, 'utf8').replace(/^\uFEFF/, '')
const rows = parseCsv(raw) // const rows = parseCsv(raw)
let upserted = 0 // let upserted = 0
let skipped = 0 // let skipped = 0
const guild = await prisma.guild.findFirst({}) // const guild = await prisma.guild.findFirst({})
for (const row of rows) { // for (const row of rows) {
const code = (row.ID || '').trim() // const code = (row.ID || '').trim()
const name = (row.DescriptionOfID || '').trim() // const name = (row.DescriptionOfID || '').trim()
const vat = parseVat((row.Vat || '').trim()) // const vat = parseVat((row.Vat || '').trim())
const type = (row.Type || '').trim() // const type = (row.Type || '').trim()
const { isPublic, isDomestic } = toBooleanFlags(type) // const { isPublic, isDomestic } = toBooleanFlags(type)
if (!code || !name) { // if (!code || !name) {
skipped++ // skipped++
continue // continue
} // }
await prisma.stockKeepingUnits.upsert({ // await prisma.stockKeepingUnits.upsert({
where: { code }, // where: { code },
create: { // create: {
code, // code,
name, // name,
VAT: vat, // VAT: vat,
guild: { // guild: {
connect: { // connect: {
id: guild?.id, // id: guild?.id,
}, // },
}, // },
is_public: isPublic, // is_public: isPublic,
is_domestic: isDomestic, // is_domestic: isDomestic,
}, // },
update: { // update: {
name, // name,
VAT: vat, // VAT: vat,
guild: { // guild: {
connect: { // connect: {
id: guild?.id, // id: guild?.id,
}, // },
}, // },
is_public: isPublic, // is_public: isPublic,
is_domestic: isDomestic, // is_domestic: isDomestic,
}, // },
}) // })
upserted++ // upserted++
} // }
} // }
main() // main()
.catch(error => { // .catch(error => {
console.error(error) // console.error(error)
process.exit(1) // process.exit(1)
}) // })
.finally(async () => { // .finally(async () => {
await prisma.$disconnect() // await prisma.$disconnect()
}) // })
+2 -4
View File
@@ -2,8 +2,6 @@ import { PrismaMariaDb } from '@prisma/adapter-mariadb'
import 'dotenv/config' import 'dotenv/config'
import { env } from 'prisma/config' import { env } from 'prisma/config'
import { PrismaClient } from '../generated/prisma/client'
const adapter = new PrismaMariaDb({ const adapter = new PrismaMariaDb({
host: env('DATABASE_HOST'), host: env('DATABASE_HOST'),
user: env('DATABASE_USER'), user: env('DATABASE_USER'),
@@ -18,6 +16,6 @@ const adapter = new PrismaMariaDb({
connectTimeout: 10000, connectTimeout: 10000,
}) })
const prisma = new PrismaClient({ adapter }) // const prisma = new PrismaClient({ adapter })
export { prisma } export { adapter as prismaAdapter }
+2
View File
@@ -17,6 +17,8 @@ const cookieParser = require('cookie-parser')
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule) const app = await NestFactory.create(AppModule)
app.enableShutdownHooks()
app.use(cookieParser()) app.use(cookieParser())
const config = new DocumentBuilder() const config = new DocumentBuilder()
@@ -1,7 +1,6 @@
import { SKUGuildType } from '@/common/enums/enums'
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
import { Type } from 'class-transformer' import { Type } from 'class-transformer'
import { IsBoolean, IsEnum, IsNumber, IsString, Min } from 'class-validator' import { IsBoolean, IsNumber, IsString, Min } from 'class-validator'
export class CreateStockKeepingUnitDto { export class CreateStockKeepingUnitDto {
@ApiProperty() @ApiProperty()
@@ -18,9 +17,9 @@ export class CreateStockKeepingUnitDto {
@Min(0) @Min(0)
VAT: number VAT: number
@ApiPropertyOptional({ enum: SKUGuildType, default: SKUGuildType.GOLD }) // @ApiPropertyOptional({ enum: SKUGuildType, default: SKUGuildType.GOLD })
@IsEnum(SKUGuildType) // @IsEnum(SKUGuildType)
type: SKUGuildType = SKUGuildType.GOLD // type: SKUGuildType = SKUGuildType.GOLD
@ApiPropertyOptional({ default: true }) @ApiPropertyOptional({ default: true })
@IsBoolean() @IsBoolean()
+2 -2
View File
@@ -25,7 +25,7 @@ import {
TspProviderType, TspProviderType,
} from '@/generated/prisma/enums' } from '@/generated/prisma/enums'
import { Injectable } from '@nestjs/common' import { Injectable } from '@nestjs/common'
import { GoldKarat, SKUGuildType, TspProviderCustomerType } from 'common/enums/enums' import { GoldKarat, TspProviderCustomerType } from 'common/enums/enums'
import { ResponseMapper } from 'common/response/response-mapper' import { ResponseMapper } from 'common/response/response-mapper'
@Injectable() @Injectable()
@@ -80,7 +80,7 @@ export class EnumsService {
TspProviderCustomerType, TspProviderCustomerType,
'TspProviderCustomerType', 'TspProviderCustomerType',
), ),
SKUGuildType: this.prepareData(SKUGuildType, 'SKUGuildType'), // SKUGuildType: this.prepareData(SKUGuildType, 'SKUGuildType'),
InvoiceTemplateType: this.prepareData(InvoiceTemplateType, 'InvoiceTemplateType'), InvoiceTemplateType: this.prepareData(InvoiceTemplateType, 'InvoiceTemplateType'),
} }
} }
+2 -10
View File
@@ -1,21 +1,13 @@
import { prismaAdapter } from '@/lib/prisma'
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common' import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
import { PrismaMariaDb } from '@prisma/adapter-mariadb'
import 'dotenv/config' import 'dotenv/config'
import { env } from 'prisma/config'
import { PrismaClient } from '../generated/prisma/client' import { PrismaClient } from '../generated/prisma/client'
import { getPrismaOptions } from './prisma-config.service' import { getPrismaOptions } from './prisma-config.service'
@Injectable() @Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() { constructor() {
const adapter = new PrismaMariaDb({ const adapter = prismaAdapter
host: env('DATABASE_HOST'),
user: env('DATABASE_USER'),
password: env('DATABASE_PASSWORD'),
database: env('DATABASE_NAME'),
port: env('DATABASE_PORT') ? Number(env('DATABASE_PORT')) : 3306,
connectionLimit: 10,
})
const prismaOptions = getPrismaOptions() const prismaOptions = getPrismaOptions()
super({ ...prismaOptions, adapter }) super({ ...prismaOptions, adapter })