diff --git a/package.json b/package.json index 54ad388..d6fb696 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/platform-express": "^11.1.19", "@nestjs/swagger": "^11.3.2", - "@prisma/client": "^7.7.0", "@prisma/adapter-mariadb": "^7.7.0", + "@prisma/client": "^7.7.0", "@types/bcrypt": "^6.0.0", "@types/multer": "^2.1.0", "bcrypt": "^6.0.0", @@ -18,13 +18,13 @@ "cookie-parser": "^1.4.7", "dayjs": "^1.11.20", "dotenv": "^17.4.2", + "ioredis": "^5.8.2", "jalaliday": "^3.1.1", "jsonwebtoken": "^9.0.3", "multer": "^2.1.1", "mysql2": "^3.22.2", "prisma": "^7.7.0", "reflect-metadata": "^0.2.2", - "ioredis": "^5.8.2", "rxjs": "^7.8.2", "uuid": "^13.0.0" }, @@ -45,6 +45,7 @@ "eslint-plugin-prettier": "^5.5.5", "globals": "^16.5.0", "jest": "^30.3.0", + "plop": "^4.0.5", "prettier": "^3.8.3", "prettier-plugin-prisma": "^5.0.0", "source-map-support": "^0.5.21", @@ -82,9 +83,10 @@ "db:reset": "tsx scripts/dump-triggers.ts && npx prisma migrate reset --force", "dump-triggers": "tsx scripts/dump-triggers.ts", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "generate": "plop", "generate:permissions": "tsx scripts/generate-permissions.ts", - "seed:sku": "tsx scripts/seedStockKeepingUnits.ts", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "seed:sku": "tsx scripts/seedStockKeepingUnits.ts", "start": "nest start", "start:debug": "nest start --debug --watch", "start:dev": "nest start --watch", diff --git a/plop-templates/controller.hbs b/plop-templates/controller.hbs new file mode 100644 index 0000000..25d94ed --- /dev/null +++ b/plop-templates/controller.hbs @@ -0,0 +1,46 @@ +import { +Body, +Controller, +Delete, +Get, +Param, +Patch, +Post, +} from '@nestjs/common'; +import { {{pascalCase name}}Service } from './{{kebabCase name}}.service'; +import { Create{{pascalCase name}}Dto } from './dto/create-{{kebabCase name}}.dto'; +import { Update{{pascalCase name}}Dto } from './dto/update-{{kebabCase name}}.dto'; + +@Controller('{{kebabCase name}}') +export class {{pascalCase name}}Controller { +constructor(private readonly service: {{pascalCase name}}Service) {} + + +@Get() +findAll() { + return this.service.findAll(); +} + +@Get(':id') +findOne(@Param('id') id: string) { + return this.service.findOne(id); +} + +@Post() +create(@Body() dto: Create{{pascalCase name}}Dto) { + return this.service.create(dto); +} + +@Patch(':id') +update( +@Param('id') id: string, +@Body() dto: Update{{pascalCase name}}Dto, +) { + return this.service.update(id, dto); +} + +@Delete(':id') +remove(@Param('id') id: string) { + return this.service.remove(id); +} +} diff --git a/plop-templates/create-dto.hbs b/plop-templates/create-dto.hbs new file mode 100644 index 0000000..2b33975 --- /dev/null +++ b/plop-templates/create-dto.hbs @@ -0,0 +1,9 @@ +import { IsString, IsNumber, IsBoolean, IsEmail } from 'class-validator'; + +export class Create{{pascalCase name}}Dto { +{{#each parsedFields}} +@{{validator}}() +{{name}}: {{type}}; + +{{/each}} +} diff --git a/plop-templates/module.hbs b/plop-templates/module.hbs new file mode 100644 index 0000000..07b5d02 --- /dev/null +++ b/plop-templates/module.hbs @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { {{pascalCase name}}Service } from './{{kebabCase name}}.service'; +import { {{pascalCase name}}Controller } from './{{kebabCase name}}.controller'; +import { PrismaService } from '{{prismaImportPath modulePath name}}'; + +@Module({ +controllers: [{{pascalCase name}}Controller], +providers: [{{pascalCase name}}Service, PrismaService], +}) +export class {{pascalCase name}}Module {} diff --git a/plop-templates/service.hbs b/plop-templates/service.hbs new file mode 100644 index 0000000..245c50f --- /dev/null +++ b/plop-templates/service.hbs @@ -0,0 +1,63 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '@/prisma/prisma.service'; +import { ResponseMapper } from '@/common/response/response-mapper' +import { Create{{pascalCase name}}Dto } from './dto/create-{{kebabCase name}}.dto'; +import { Update{{pascalCase name}}Dto } from './dto/update-{{kebabCase name}}.dto'; + +@Injectable() +export class {{pascalCase name}}Service { +constructor(private readonly prisma: PrismaService) {} + +private readonly summarySelect = { + id: true, +} + +private readonly select = { + ...this.summarySelect, +} + +private readonly where = () => ({}) + + +async findAll() { + const items = await this.prisma.{{camelCase name}}.findMany({ + where: this.where(), + select: this.summarySelect, + }); + return ResponseMapper.list(items) +} + +async findOne(id: string) { + const item = this.prisma.{{camelCase name}}.findUnique({ + where: {...this.where(), id }, + select: this.select, + }); + + return ResponseMapper.single(item) +} + +async create(createDto: Create{{pascalCase name}}Dto) { + const item = this.prisma.{{camelCase name}}.create({ + data: createDto, + select: this.select, + }); + + return ResponseMapper.create(item) +} + +async update(id: string, updateDto: Update{{pascalCase name}}Dto) { + const item = this.prisma.{{camelCase name}}.update({ + where: { id }, + data: updateDto, + select: this.select, + }); + + return ResponseMapper.update(item) +} + +async remove(id: string) { + return this.prisma.{{camelCase name}}.delete({ + where: { id }, + }); + } +} diff --git a/plop-templates/update-dto.hbs b/plop-templates/update-dto.hbs new file mode 100644 index 0000000..37f2ecb --- /dev/null +++ b/plop-templates/update-dto.hbs @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger' +import { Create{{pascalCase name}}Dto } from './create-{{kebabCase name}}.dto'; + +export class Update{{pascalCase name}}Dto extends PartialType(Create{{pascalCase name}}Dto) {} diff --git a/plopfile.js b/plopfile.js new file mode 100644 index 0000000..3908a15 --- /dev/null +++ b/plopfile.js @@ -0,0 +1,125 @@ +const path = require('path') + +module.exports = function (plop) { + // ------------------------- + // Case helpers + // ------------------------- + const toKebab = str => + str + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/\s+/g, '-') + .toLowerCase() + + const toPascal = str => + str + .split(/[-_\s]+/) + .map(part => part.charAt(0).toUpperCase() + part.slice(1)) + .join('') + + const toCamel = str => { + const pascal = toPascal(str) + return pascal.charAt(0).toLowerCase() + pascal.slice(1) + } + + plop.setHelper('kebabCase', toKebab) + plop.setHelper('pascalCase', toPascal) + plop.setHelper('camelCase', toCamel) + + // ------------------------- + // Dynamic relative import helper + // ------------------------- + plop.setHelper('prismaImportPath', (modulePath, name) => { + return '@/prisma/prisma.service' + }) + + // ------------------------- + // Generator + // ------------------------- + plop.setGenerator('resource', { + description: 'Generate Nested NestJS Prisma Resource', + prompts: [ + { + type: 'input', + name: 'name', + message: 'Module name (e.g. user, blog-post):', + }, + { + type: 'input', + name: 'modulePath', + message: 'Nested path inside modules (e.g. admin/users) — leave empty for root:', + default: '', + }, + { + type: 'input', + name: 'fields', + message: + 'DTO fields (comma-separated, e.g. name:string,email:email,age:number,isActive:boolean):', + }, + ], + actions(data) { + // sanitize path + data.modulePath = data.modulePath.replace(/^\/+|\/+$/g, '') + + // Parse fields + const rawFields = data.fields || '' + data.parsedFields = rawFields + .split(',') + .filter(Boolean) + .map(field => { + const [name, type] = field.split(':').map(v => v.trim()) + + let validator = 'IsString' + let finalType = 'string' + + if (type === 'number') { + validator = 'IsNumber' + finalType = 'number' + } + + if (type === 'boolean') { + validator = 'IsBoolean' + finalType = 'boolean' + } + + if (type === 'email') { + validator = 'IsEmail' + finalType = 'string' + } + + return { name, validator, type: finalType } + }) + + const basePath = data.modulePath + ? `src/modules/${data.modulePath}/{{kebabCase name}}` + : `src/modules/{{kebabCase name}}` + + return [ + { + type: 'add', + path: `${basePath}/{{kebabCase name}}.module.ts`, + templateFile: 'plop-templates/module.hbs', + }, + { + type: 'add', + path: `${basePath}/{{kebabCase name}}.service.ts`, + templateFile: 'plop-templates/service.hbs', + }, + { + type: 'add', + path: `${basePath}/{{kebabCase name}}.controller.ts`, + templateFile: 'plop-templates/controller.hbs', + }, + { + type: 'add', + path: `${basePath}/dto/create-{{kebabCase name}}.dto.ts`, + templateFile: 'plop-templates/create-dto.hbs', + }, + { + type: 'add', + path: `${basePath}/dto/update-{{kebabCase name}}.dto.ts`, + templateFile: 'plop-templates/update-dto.hbs', + }, + ] + }, + }) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e8bc08..8ce869c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,6 +132,9 @@ importers: jest: specifier: ^30.3.0 version: 30.3.0(@types/node@22.19.17)(ts-node@10.9.2(@types/node@22.19.17)(typescript@5.9.3)) + plop: + specifier: ^4.0.5 + version: 4.0.5(@types/node@22.19.17) prettier: specifier: ^3.8.3 version: 3.8.3 @@ -1605,12 +1608,18 @@ packages: '@types/express@5.0.6': resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/fined@1.1.5': + resolution: {integrity: sha512-2N93vadEGDFhASTIRbizbl4bNqpMOId5zZfj6hHqYZfEzEfO9onnU4Im8xvzo8uudySDveDHBOOSlTWf38ErfQ==, tarball: https://hub.megan.ir/repository/npm/@types/fined/-/fined-1.1.5.tgz} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/inquirer@9.0.9': + resolution: {integrity: sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==, tarball: https://hub.megan.ir/repository/npm/@types/inquirer/-/inquirer-9.0.9.tgz} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1629,6 +1638,9 @@ packages: '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/liftoff@4.0.3': + resolution: {integrity: sha512-UgbL2kR5pLrWICvr8+fuSg0u43LY250q7ZMkC+XKC3E+rs/YBDEnQIzsnhU5dYsLlwMi3R75UvCL87pObP1sxw==, tarball: https://hub.megan.ir/repository/npm/@types/liftoff/-/liftoff-4.0.3.tgz} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} @@ -1644,6 +1656,9 @@ packages: '@types/node@24.12.2': resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + '@types/picomatch@4.0.3': + resolution: {integrity: sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ==, tarball: https://hub.megan.ir/repository/npm/@types/picomatch/-/picomatch-4.0.3.tgz} + '@types/qs@6.15.0': resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} @@ -1668,6 +1683,9 @@ packages: '@types/supertest@6.0.3': resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + '@types/through@0.0.33': + resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==, tarball: https://hub.megan.ir/repository/npm/@types/through/-/through-0.0.33.tgz} + '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} @@ -1997,6 +2015,14 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-each@1.0.1: + resolution: {integrity: sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==, tarball: https://hub.megan.ir/repository/npm/array-each/-/array-each-1.0.1.tgz} + engines: {node: '>=0.10.0'} + + array-slice@1.1.0: + resolution: {integrity: sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==, tarball: https://hub.megan.ir/repository/npm/array-slice/-/array-slice-1.1.0.tgz} + engines: {node: '>=0.10.0'} + array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} @@ -2145,6 +2171,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==, tarball: https://hub.megan.ir/repository/npm/change-case/-/change-case-5.4.4.tgz} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -2361,6 +2390,10 @@ packages: destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==, tarball: https://hub.megan.ir/repository/npm/detect-file/-/detect-file-1.0.0.tgz} + engines: {node: '>=0.10.0'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -2372,6 +2405,9 @@ packages: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==, tarball: https://hub.megan.ir/repository/npm/dlv/-/dlv-1.1.3.tgz} + dotenv-expand@12.0.3: resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} engines: {node: '>=12'} @@ -2570,6 +2606,10 @@ packages: resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} engines: {node: '>= 0.8.0'} + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==, tarball: https://hub.megan.ir/repository/npm/expand-tilde/-/expand-tilde-2.0.2.tgz} + engines: {node: '>=0.10.0'} + expect@30.3.0: resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2581,6 +2621,9 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, tarball: https://hub.megan.ir/repository/npm/extend/-/extend-3.0.2.tgz} + fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} @@ -2646,6 +2689,18 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + findup-sync@5.0.0: + resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==, tarball: https://hub.megan.ir/repository/npm/findup-sync/-/findup-sync-5.0.0.tgz} + engines: {node: '>= 10.13.0'} + + fined@2.0.0: + resolution: {integrity: sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==, tarball: https://hub.megan.ir/repository/npm/fined/-/fined-2.0.0.tgz} + engines: {node: '>= 10.13.0'} + + flagged-respawn@2.0.0: + resolution: {integrity: sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==, tarball: https://hub.megan.ir/repository/npm/flagged-respawn/-/flagged-respawn-2.0.0.tgz} + engines: {node: '>= 10.13.0'} + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -2653,6 +2708,14 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + for-in@1.0.2: + resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==, tarball: https://hub.megan.ir/repository/npm/for-in/-/for-in-1.0.2.tgz} + engines: {node: '>=0.10.0'} + + for-own@1.0.0: + resolution: {integrity: sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==, tarball: https://hub.megan.ir/repository/npm/for-own/-/for-own-1.0.0.tgz} + engines: {node: '>=0.10.0'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -2755,6 +2818,14 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==, tarball: https://hub.megan.ir/repository/npm/global-modules/-/global-modules-1.0.0.tgz} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==, tarball: https://hub.megan.ir/repository/npm/global-prefix/-/global-prefix-1.0.2.tgz} + engines: {node: '>=0.10.0'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -2797,6 +2868,10 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==, tarball: https://hub.megan.ir/repository/npm/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz} + engines: {node: '>=0.10.0'} + hono@4.12.14: resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==} engines: {node: '>=16.9.0'} @@ -2854,6 +2929,17 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, tarball: https://hub.megan.ir/repository/npm/ini/-/ini-1.3.8.tgz} + + inquirer@9.3.8: + resolution: {integrity: sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==, tarball: https://hub.megan.ir/repository/npm/inquirer/-/inquirer-9.3.8.tgz} + engines: {node: '>=18'} + + interpret@3.1.1: + resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==, tarball: https://hub.megan.ir/repository/npm/interpret/-/interpret-3.1.1.tgz} + engines: {node: '>=10.13.0'} + ioredis@5.10.1: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==, tarball: https://hub.megan.ir/repository/npm/ioredis/-/ioredis-5.10.1.tgz} engines: {node: '>=12.22.0'} @@ -2862,9 +2948,17 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==, tarball: https://hub.megan.ir/repository/npm/is-absolute/-/is-absolute-1.0.0.tgz} + engines: {node: '>=0.10.0'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==, tarball: https://hub.megan.ir/repository/npm/is-core-module/-/is-core-module-2.16.2.tgz} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2889,23 +2983,47 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==, tarball: https://hub.megan.ir/repository/npm/is-plain-object/-/is-plain-object-5.0.0.tgz} + engines: {node: '>=0.10.0'} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==, tarball: https://hub.megan.ir/repository/npm/is-relative/-/is-relative-1.0.0.tgz} + engines: {node: '>=0.10.0'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==, tarball: https://hub.megan.ir/repository/npm/is-unc-path/-/is-unc-path-1.0.0.tgz} + engines: {node: '>=0.10.0'} + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==, tarball: https://hub.megan.ir/repository/npm/is-windows/-/is-windows-1.0.2.tgz} + engines: {node: '>=0.10.0'} + + isbinaryfile@5.0.7: + resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==, tarball: https://hub.megan.ir/repository/npm/isbinaryfile/-/isbinaryfile-5.0.7.tgz} + engines: {node: '>= 18.0.0'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==, tarball: https://hub.megan.ir/repository/npm/isobject/-/isobject-3.0.1.tgz} + engines: {node: '>=0.10.0'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -3140,6 +3258,10 @@ packages: libphonenumber-js@1.12.41: resolution: {integrity: sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA==} + liftoff@5.0.1: + resolution: {integrity: sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==, tarball: https://hub.megan.ir/repository/npm/liftoff/-/liftoff-5.0.1.tgz} + engines: {node: '>=10.13.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -3229,6 +3351,10 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==, tarball: https://hub.megan.ir/repository/npm/map-cache/-/map-cache-0.2.2.tgz} + engines: {node: '>=0.10.0'} + mariadb@3.4.5: resolution: {integrity: sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==} engines: {node: '>= 14'} @@ -3314,6 +3440,10 @@ packages: resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} engines: {node: '>= 10.16.0'} + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==, tarball: https://hub.megan.ir/repository/npm/mute-stream/-/mute-stream-1.0.0.tgz} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -3332,6 +3462,9 @@ packages: resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} engines: {node: '>=8.0.0'} + nanospinner@1.2.2: + resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==, tarball: https://hub.megan.ir/repository/npm/nanospinner/-/nanospinner-1.2.2.tgz} + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -3367,6 +3500,10 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-plop@0.32.3: + resolution: {integrity: sha512-tn+OxutdqhvoByKJ7p84FZBSUDfUB76bcvj0ugLBvgE9V52LFcnz8cauCDKi6otnctvFCqa9XkrU35pBY5Baig==, tarball: https://hub.megan.ir/repository/npm/node-plop/-/node-plop-0.32.3.tgz} + engines: {node: '>=18'} + node-releases@2.0.37: resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} @@ -3391,6 +3528,14 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object.defaults@1.1.0: + resolution: {integrity: sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==, tarball: https://hub.megan.ir/repository/npm/object.defaults/-/object.defaults-1.1.0.tgz} + engines: {node: '>=0.10.0'} + + object.pick@1.3.0: + resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==, tarball: https://hub.megan.ir/repository/npm/object.pick/-/object.pick-1.3.0.tgz} + engines: {node: '>=0.10.0'} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -3440,10 +3585,18 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-filepath@1.0.2: + resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==, tarball: https://hub.megan.ir/repository/npm/parse-filepath/-/parse-filepath-1.0.2.tgz} + engines: {node: '>=0.8'} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==, tarball: https://hub.megan.ir/repository/npm/parse-passwd/-/parse-passwd-1.0.0.tgz} + engines: {node: '>=0.10.0'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -3464,6 +3617,17 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, tarball: https://hub.megan.ir/repository/npm/path-parse/-/path-parse-1.0.7.tgz} + + path-root-regex@0.1.2: + resolution: {integrity: sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==, tarball: https://hub.megan.ir/repository/npm/path-root-regex/-/path-root-regex-0.1.2.tgz} + engines: {node: '>=0.10.0'} + + path-root@0.1.1: + resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==, tarball: https://hub.megan.ir/repository/npm/path-root/-/path-root-0.1.1.tgz} + engines: {node: '>=0.10.0'} + path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} @@ -3486,7 +3650,7 @@ packages: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, tarball: https://hub.megan.ir/repository/npm/picocolors/-/picocolors-1.1.1.tgz} picomatch@2.3.2: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} @@ -3507,6 +3671,11 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + plop@4.0.5: + resolution: {integrity: sha512-pJz6oWC9LyBp5mBrRp8AUV2RNiuGW+t/HOs4zwN+b/3YxoObZOOFvjn1mJMpAeKi2pbXADMFOOVQVTVXEdDHDw==, tarball: https://hub.megan.ir/repository/npm/plop/-/plop-4.0.5.tgz} + engines: {node: '>=18'} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -3603,6 +3772,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==, tarball: https://hub.megan.ir/repository/npm/rechoir/-/rechoir-0.8.0.tgz} + engines: {node: '>= 10.13.0'} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==, tarball: https://hub.megan.ir/repository/npm/redis-errors/-/redis-errors-1.2.0.tgz} engines: {node: '>=4'} @@ -3629,6 +3802,10 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==, tarball: https://hub.megan.ir/repository/npm/resolve-dir/-/resolve-dir-1.0.1.tgz} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3640,6 +3817,11 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==, tarball: https://hub.megan.ir/repository/npm/resolve/-/resolve-1.22.12.tgz} + engines: {node: '>= 0.4'} + hasBin: true + restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -3652,6 +3834,10 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==, tarball: https://hub.megan.ir/repository/npm/run-async/-/run-async-3.0.0.tgz} + engines: {node: '>=0.12.0'} + rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -3842,6 +4028,10 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, tarball: https://hub.megan.ir/repository/npm/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz} + engines: {node: '>= 0.4'} + swagger-ui-dist@5.32.4: resolution: {integrity: sha512-0AADFFQNJzExEN49SrD/34Nn9cxNxVLiydYl2MBwSZFPVXNkVwC/EFAjoezGGqE8oDegiDC+p47t8lKObCinMQ==} @@ -3890,6 +4080,9 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + title-case@4.3.2: + resolution: {integrity: sha512-I/nkcBo73mO42Idfv08jhInV61IMb61OdIFxk+B4Gu1oBjWBPOLmhZdsli+oJCVaD+86pYQA93cJfFt224ZFAA==, tarball: https://hub.megan.ir/repository/npm/title-case/-/title-case-4.3.2.tgz} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -4027,6 +4220,10 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} + unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==, tarball: https://hub.megan.ir/repository/npm/unc-path-regex/-/unc-path-regex-0.1.2.tgz} + engines: {node: '>=0.10.0'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -4067,6 +4264,10 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + v8flags@4.0.1: + resolution: {integrity: sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==, tarball: https://hub.megan.ir/repository/npm/v8flags/-/v8flags-4.0.1.tgz} + engines: {node: '>= 10.13.0'} + valibot@1.2.0: resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} peerDependencies: @@ -4111,6 +4312,10 @@ packages: webpack-cli: optional: true + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==, tarball: https://hub.megan.ir/repository/npm/which/-/which-1.3.1.tgz} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -6126,10 +6331,17 @@ snapshots: '@types/express-serve-static-core': 5.1.1 '@types/serve-static': 2.2.0 + '@types/fined@1.1.5': {} + '@types/geojson@7946.0.16': {} '@types/http-errors@2.0.5': {} + '@types/inquirer@9.0.9': + dependencies: + '@types/through': 0.0.33 + rxjs: 7.8.2 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -6152,6 +6364,11 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 22.19.17 + '@types/liftoff@4.0.3': + dependencies: + '@types/fined': 1.1.5 + '@types/node': 22.19.17 + '@types/methods@1.1.4': {} '@types/ms@2.1.0': {} @@ -6168,6 +6385,8 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/picomatch@4.0.3': {} + '@types/qs@6.15.0': {} '@types/range-parser@1.2.7': {} @@ -6199,6 +6418,10 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/through@0.0.33': + dependencies: + '@types/node': 22.19.17 + '@types/validator@13.15.10': {} '@types/yargs-parser@21.0.3': {} @@ -6524,6 +6747,10 @@ snapshots: argparse@2.0.1: {} + array-each@1.0.1: {} + + array-slice@1.1.0: {} + array-timsort@1.0.3: {} asap@2.0.6: {} @@ -6707,6 +6934,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + char-regex@1.0.2: {} chardet@2.1.1: {} @@ -6872,6 +7101,8 @@ snapshots: destr@2.0.5: {} + detect-file@1.0.0: {} + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -6881,6 +7112,8 @@ snapshots: diff@4.0.4: {} + dlv@1.1.3: {} + dotenv-expand@12.0.3: dependencies: dotenv: 16.6.1 @@ -7098,6 +7331,10 @@ snapshots: exit-x@0.2.2: {} + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + expect@30.3.0: dependencies: '@jest/expect-utils': 30.3.0 @@ -7142,6 +7379,8 @@ snapshots: exsolve@1.0.8: {} + extend@3.0.2: {} + fast-check@3.23.2: dependencies: pure-rand: 6.1.0 @@ -7214,6 +7453,23 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + findup-sync@5.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + + fined@2.0.0: + dependencies: + expand-tilde: 2.0.2 + is-plain-object: 5.0.0 + object.defaults: 1.1.0 + object.pick: 1.3.0 + parse-filepath: 1.0.2 + + flagged-respawn@2.0.0: {} + flat-cache@4.0.1: dependencies: flatted: 3.4.2 @@ -7221,6 +7477,12 @@ snapshots: flatted@3.4.2: {} + for-in@1.0.2: {} + + for-own@1.0.0: + dependencies: + for-in: 1.0.2 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -7351,6 +7613,20 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + globals@14.0.0: {} globals@16.5.0: {} @@ -7384,6 +7660,10 @@ snapshots: dependencies: function-bind: 1.1.2 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + hono@4.12.14: {} html-escaper@2.0.2: {} @@ -7433,6 +7713,27 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + + inquirer@9.3.8(@types/node@22.19.17): + dependencies: + '@inquirer/external-editor': 1.0.3(@types/node@22.19.17) + '@inquirer/figures': 1.0.15 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + transitivePeerDependencies: + - '@types/node' + + interpret@3.1.1: {} + ioredis@5.10.1: dependencies: '@ioredis/commands': 1.5.1 @@ -7449,8 +7750,17 @@ snapshots: ipaddr.js@1.9.1: {} + is-absolute@1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + is-arrayish@0.2.1: {} + is-core-module@2.16.2: + dependencies: + hasown: 2.0.3 + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -7465,16 +7775,32 @@ snapshots: is-number@7.0.0: {} + is-plain-object@5.0.0: {} + is-promise@4.0.0: {} is-property@1.0.2: {} + is-relative@1.0.0: + dependencies: + is-unc-path: 1.0.0 + is-stream@2.0.1: {} + is-unc-path@1.0.0: + dependencies: + unc-path-regex: 0.1.2 + is-unicode-supported@0.1.0: {} + is-windows@1.0.2: {} + + isbinaryfile@5.0.7: {} + isexe@2.0.0: {} + isobject@3.0.1: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: @@ -7907,6 +8233,16 @@ snapshots: libphonenumber-js@1.12.41: {} + liftoff@5.0.1: + dependencies: + extend: 3.0.2 + findup-sync: 5.0.0 + fined: 2.0.0 + flagged-respawn: 2.0.0 + is-plain-object: 5.0.0 + rechoir: 0.8.0 + resolve: 1.22.12 + lines-and-columns@1.2.4: {} load-esm@1.0.3: {} @@ -7976,6 +8312,8 @@ snapshots: dependencies: tmpl: 1.0.5 + map-cache@0.2.2: {} + mariadb@3.4.5: dependencies: '@types/geojson': 7946.0.16 @@ -8046,6 +8384,8 @@ snapshots: concat-stream: 2.0.0 type-is: 1.6.18 + mute-stream@1.0.0: {} + mute-stream@2.0.0: {} mysql2@3.15.3: @@ -8076,6 +8416,10 @@ snapshots: dependencies: lru.min: 1.1.4 + nanospinner@1.2.2: + dependencies: + picocolors: 1.1.1 + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -8098,6 +8442,21 @@ snapshots: node-int64@0.4.0: {} + node-plop@0.32.3(@types/node@22.19.17): + dependencies: + '@types/inquirer': 9.0.9 + '@types/picomatch': 4.0.3 + change-case: 5.4.4 + dlv: 1.1.3 + handlebars: 4.7.9 + inquirer: 9.3.8(@types/node@22.19.17) + isbinaryfile: 5.0.7 + resolve: 1.22.12 + tinyglobby: 0.2.16 + title-case: 4.3.2 + transitivePeerDependencies: + - '@types/node' + node-releases@2.0.37: {} normalize-path@3.0.0: {} @@ -8116,6 +8475,17 @@ snapshots: object-inspect@1.13.4: {} + object.defaults@1.1.0: + dependencies: + array-each: 1.0.1 + array-slice: 1.1.0 + for-own: 1.0.0 + isobject: 3.0.1 + + object.pick@1.3.0: + dependencies: + isobject: 3.0.1 + ohash@2.0.11: {} on-finished@2.4.1: @@ -8175,6 +8545,12 @@ snapshots: dependencies: callsites: 3.1.0 + parse-filepath@1.0.2: + dependencies: + is-absolute: 1.0.0 + map-cache: 0.2.2 + path-root: 0.1.1 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.29.0 @@ -8182,6 +8558,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-passwd@1.0.0: {} + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -8192,6 +8570,14 @@ snapshots: path-key@3.1.1: {} + path-parse@1.0.7: {} + + path-root-regex@0.1.2: {} + + path-root@0.1.1: + dependencies: + path-root-regex: 0.1.2 + path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 @@ -8228,6 +8614,18 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + plop@4.0.5(@types/node@22.19.17): + dependencies: + '@types/liftoff': 4.0.3 + interpret: 3.1.1 + liftoff: 5.0.1 + nanospinner: 1.2.2 + node-plop: 0.32.3(@types/node@22.19.17) + picocolors: 1.1.1 + v8flags: 4.0.1 + transitivePeerDependencies: + - '@types/node' + pluralize@8.0.0: {} postgres@3.4.7: {} @@ -8320,6 +8718,10 @@ snapshots: readdirp@4.1.2: {} + rechoir@0.8.0: + dependencies: + resolve: 1.22.12 + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -8338,12 +8740,24 @@ snapshots: dependencies: resolve-from: 5.0.0 + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@3.1.0: dependencies: onetime: 5.1.2 @@ -8361,6 +8775,8 @@ snapshots: transitivePeerDependencies: - supports-color + run-async@3.0.0: {} + rxjs@7.8.1: dependencies: tslib: 2.8.1 @@ -8568,6 +8984,8 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + swagger-ui-dist@5.32.4: dependencies: '@scarf/scarf': 1.4.0 @@ -8608,6 +9026,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + title-case@4.3.2: {} + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -8741,6 +9161,8 @@ snapshots: uint8array-extras@1.5.0: {} + unc-path-regex@0.1.2: {} + undici-types@6.21.0: {} undici-types@7.16.0: {} @@ -8795,6 +9217,8 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + v8flags@4.0.1: {} + valibot@1.2.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -8852,6 +9276,10 @@ snapshots: - esbuild - uglify-js + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/src/common/guards/auth-pos.guard.ts b/src/common/guards/auth-pos.guard.ts index ef94417..6f57657 100644 --- a/src/common/guards/auth-pos.guard.ts +++ b/src/common/guards/auth-pos.guard.ts @@ -52,85 +52,5 @@ export class PosGuard { } return true - // const pos = await this.prisma.pos.findUnique({ - // where: { - // id: posId, - // account_id: tokenPayload.account_id, - // }, - // select: { - // complex: { - // select: { - // id: true, - // business_activity: { - // select: { - // id: true, - // license_activation: { - // select: { - // expires_at: true, - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // }) - - // if (!pos) { - // throw new ForbiddenException('شما دسترسی لازم را ندارید.') - // } - - // if (req.method !== 'GET') { - // if (!pos.complex.business_activity.license_activation) { - // throw new ForbiddenException('برای کاربر شما لایسنس ایجاد نشده است.') - // } - // if ( - // pos.complex.business_activity.license_activation.expires_at && - // new Date().getTime() > - // new Date(pos.complex.business_activity.license_activation.expires_at).getTime() - // ) { - // throw new ForbiddenException('لایسنس شما منقضی شده است.') - // } - // } - - // const foundedAccount = await this.prisma.consumerAccount.findUnique({ - // where: { - // id: tokenPayload.account_id, - // }, - // select: { - // role: true, - // }, - // }) - - // if (foundedAccount?.role === 'OWNER') { - // return true - // } - - // const accountPermissions = await this.prisma.permissionConsumer.findUnique({ - // where: { - // consumer_account_id: tokenPayload.account_id, - // }, - // select: { - // pos_permissions: true, - // business_permissions: true, - // complex_permissions: true, - // }, - // }) - - // if (accountPermissions?.pos_permissions.some(p => p.pos_id === posId)) { - // return true - // } - // if ( - // accountPermissions?.complex_permissions.some(p => p.complex_id === pos.complex.id) - // ) { - // return true - // } - // if ( - // accountPermissions?.business_permissions.some( - // p => p.business_id === pos.complex.business_activity.id, - // ) - // ) { - // return true - // } } } diff --git a/src/common/utils/redisKeyMaker/index.ts b/src/common/utils/redisKeyMaker/index.ts index cf82026..6c2a997 100644 --- a/src/common/utils/redisKeyMaker/index.ts +++ b/src/common/utils/redisKeyMaker/index.ts @@ -19,9 +19,9 @@ export class RedisKeyMaker extends PartnerKeyMaker { static guildStockKeepingUnitsList = GuildKeyMaker.guildStockKeepingUnitsList static guildGoodsList = GuildKeyMaker.guildGoodsList - static posInfo = PosKeyMaker.posInfo - static posMe = PosKeyMaker.posMe - static posGoodsList = PosKeyMaker.posGoodsList + static posInfo = PosKeyMaker.info + static posMe = PosKeyMaker.me + static posGoodsList = PosKeyMaker.goodList static enumsAll = EnumKeyMaker.enumsAll static enumsValues = EnumKeyMaker.enumsValues diff --git a/src/common/utils/redisKeyMaker/pos.ts b/src/common/utils/redisKeyMaker/pos.ts index 7e2b1c3..a71bd14 100644 --- a/src/common/utils/redisKeyMaker/pos.ts +++ b/src/common/utils/redisKeyMaker/pos.ts @@ -1,13 +1,17 @@ export class PosKeyMaker { - static posInfo(posId: string): string { + static info(posId: string): string { return `pos:${posId}:info` } - static posMe(accountId: string, posId: string): string { + static me(accountId: string, posId: string): string { return `pos:${posId}:me:${accountId}` } - static posGoodsList(businessActivityId: string, guildId: string): string { - return `pos:ba:${businessActivityId}:guild:${guildId}:goods:list` + static goodList(guildId: string, businessActivityId: string): string { + return `pos:goods:list:guildId:${guildId}:ba:${businessActivityId}` + } + + static goodListByGuildPattern(guildId: string): string { + return `pos:goods:list:guildId:${guildId}:ba:*` } } diff --git a/src/modules/admin/guilds/cache/admin-guild-cache-invalidation.service.ts b/src/modules/admin/guilds/cache/admin-guild-cache-invalidation.service.ts index 31e9b16..0030a8c 100644 --- a/src/modules/admin/guilds/cache/admin-guild-cache-invalidation.service.ts +++ b/src/modules/admin/guilds/cache/admin-guild-cache-invalidation.service.ts @@ -12,6 +12,6 @@ export class AdminGuildCacheInvalidationService { async invalidateGoodsList(guildId: string): Promise { await this.redisService.delete(RedisKeyMaker.guildGoodsList(guildId)) - await this.posCacheInvalidationService.invalidateGoodsListByGuild(guildId) + await this.posCacheInvalidationService.invalidatePosGoodsByGuildPattern(guildId) } } diff --git a/src/modules/admin/guilds/goods/goods.controller.ts b/src/modules/admin/guilds/goods/goods.controller.ts index fd9bae9..612610c 100644 --- a/src/modules/admin/guilds/goods/goods.controller.ts +++ b/src/modules/admin/guilds/goods/goods.controller.ts @@ -39,8 +39,12 @@ export class GoodsController { }, }, }) - create(@Body() data: CreateGuildGoodDto, @UploadedFile() file: Express.Multer.File) { - return this.goodsService.create(data, file) + create( + @Param('guildId') guildId: string, + @Body() data: CreateGuildGoodDto, + @UploadedFile() file: Express.Multer.File, + ) { + return this.goodsService.create(guildId, data, file) } @Patch(':id') @@ -55,10 +59,11 @@ export class GoodsController { }, }) update( + @Param('guildId') guildId: string, @Param('id') id: string, @Body() data: UpdateGuildGoodDto, @UploadedFile() file: Express.Multer.File, ) { - return this.goodsService.update(id, data, file) + return this.goodsService.update(guildId, id, data, file) } } diff --git a/src/modules/admin/guilds/goods/goods.service.ts b/src/modules/admin/guilds/goods/goods.service.ts index fb7c613..616ee8e 100644 --- a/src/modules/admin/guilds/goods/goods.service.ts +++ b/src/modules/admin/guilds/goods/goods.service.ts @@ -91,12 +91,8 @@ export class GoodsService { return ResponseMapper.single(good) } - async create(data: CreateGuildGoodDto, file: Express.Multer.File) { + async create(guildId: string, data: CreateGuildGoodDto, file: Express.Multer.File) { const { category_id, measure_unit_id, sku_id, ...rest } = data - const category = await this.prisma.goodCategory.findUnique({ - where: { id: category_id }, - select: { guild_id: true }, - }) const good = await this.prisma.$transaction(async tx => { let image_url = '' @@ -133,28 +129,21 @@ export class GoodsService { }) }) - if (category?.guild_id) { - await this.cacheInvalidationService.invalidateGoodsList(category.guild_id) - } + await this.cacheInvalidationService.invalidateGoodsList(guildId) return ResponseMapper.create(good) } - async update(id: string, data: UpdateGuildGoodDto, file: Express.Multer.File) { + async update( + guildId: string, + id: string, + data: UpdateGuildGoodDto, + file: Express.Multer.File, + ) { const { category_id, measure_unit_id, sku_id, ...rest } = data - const prevGood = await this.prisma.good.findUnique({ - where: { id }, - select: { category: { select: { guild_id: true } } }, - }) - const nextCategory = category_id - ? await this.prisma.goodCategory.findUnique({ - where: { id: category_id }, - select: { guild_id: true }, - }) - : null const good = await this.prisma.$transaction(async tx => { - let image_url = '' + let image_url = undefined as string | undefined if (file) { const uploadedUrl = await this.uploaderService.uploadFile( file, @@ -198,17 +187,7 @@ export class GoodsService { }) }) - const cacheGuildIds = new Set() - if (prevGood?.category?.guild_id) { - cacheGuildIds.add(prevGood.category.guild_id) - } - if (nextCategory?.guild_id) { - cacheGuildIds.add(nextCategory.guild_id) - } - - for (const guildId of cacheGuildIds) { - await this.cacheInvalidationService.invalidateGoodsList(guildId) - } + await this.cacheInvalidationService.invalidateGoodsList(guildId) return ResponseMapper.create(good) } diff --git a/src/modules/consumer/business-activities/goods/goods.controller.ts b/src/modules/consumer/business-activities/goods/goods.controller.ts index 9a942e7..9364fd3 100644 --- a/src/modules/consumer/business-activities/goods/goods.controller.ts +++ b/src/modules/consumer/business-activities/goods/goods.controller.ts @@ -36,7 +36,6 @@ export class ConsumerBusinessActivityGoodsController { async findOne( @TokenAccount('userId') consumer_id: string, @Param('businessActivityId') businessActivityId: string, - @Param('id') id: string, ): Promise { return this.service.findOne(consumer_id, businessActivityId, id) diff --git a/src/modules/consumer/business-activities/goods/goods.service.ts b/src/modules/consumer/business-activities/goods/goods.service.ts index 2a06118..16fcb71 100644 --- a/src/modules/consumer/business-activities/goods/goods.service.ts +++ b/src/modules/consumer/business-activities/goods/goods.service.ts @@ -111,10 +111,15 @@ export class ConsumerBusinessActivityGoodsService { }, }, }, + select: this.defaultSelect, }) - await this.posCacheInvalidationService.invalidateGoodsListByBusinessActivity( - business_activity_id, - ) + + if (good.category) { + await this.posCacheInvalidationService.invalidatePosGoodsList( + good.category.guild_id!, + business_activity_id, + ) + } return ResponseMapper.create(good) } @@ -127,7 +132,7 @@ export class ConsumerBusinessActivityGoodsService { ) { const good = await this.prisma.$transaction(async tx => { const { category_id, sku_id, measure_unit_id, ...rest } = data - let image_url = '' + let image_url = undefined as string | undefined if (file) { const uploadedUrl = await this.uploaderService.uploadFile( file, @@ -177,11 +182,16 @@ export class ConsumerBusinessActivityGoodsService { }, ...rest, }, + select: this.defaultSelect, }) }) - await this.posCacheInvalidationService.invalidateGoodsListByBusinessActivity( - business_activity_id, - ) + + if (good.category) { + await this.posCacheInvalidationService.invalidatePosGoodsList( + good.category.guild_id!, + business_activity_id, + ) + } return ResponseMapper.update(good) } diff --git a/src/modules/pos/cache/pos-cache-invalidation.service.ts b/src/modules/pos/cache/pos-cache-invalidation.service.ts index 7aa800a..6af624e 100644 --- a/src/modules/pos/cache/pos-cache-invalidation.service.ts +++ b/src/modules/pos/cache/pos-cache-invalidation.service.ts @@ -1,25 +1,19 @@ -import { Injectable } from '@nestjs/common' +import { PosKeyMaker } from '@/common/utils' import { RedisService } from '@/redis/redis.service' +import { Injectable } from '@nestjs/common' @Injectable() export class PosCacheInvalidationService { constructor(private readonly redisService: RedisService) {} - async invalidateGoodsListByGuild(guildId: string): Promise { - const client = await this.redisService.getClient() - const keys = await client.keys(`pos:ba:*:guild:${guildId}:goods:list`) - if (keys.length > 0) { - await client.del(keys) - } - } - - async invalidateGoodsListByBusinessActivity( + async invalidatePosGoodsList( + guildId: string, businessActivityId: string, ): Promise { - const client = await this.redisService.getClient() - const keys = await client.keys(`pos:ba:${businessActivityId}:guild:*:goods:list`) - if (keys.length > 0) { - await client.del(keys) - } + this.redisService.delete(PosKeyMaker.goodList(guildId, businessActivityId)) + } + + async invalidatePosGoodsByGuildPattern(guildId: string): Promise { + this.redisService.deleteByPattern(PosKeyMaker.goodListByGuildPattern(guildId)) } } diff --git a/src/modules/pos/goods/dto/create-good.dto.ts b/src/modules/pos/goods/dto/create-good.dto.ts index 1874850..4ff8790 100644 --- a/src/modules/pos/goods/dto/create-good.dto.ts +++ b/src/modules/pos/goods/dto/create-good.dto.ts @@ -1,47 +1,6 @@ -import { GoodPricingModel } from '@/generated/prisma/enums' -import { ApiProperty, PartialType } from '@nestjs/swagger' -import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator' +import { CreateGuildGoodDto } from '@/modules/admin/guilds/goods/dto/create-good.dto' +import { PartialType } from '@nestjs/swagger' -export class CreateGoodDto { - @IsString() - @ApiProperty() - name: string - - @IsOptional() - @IsString() - @ApiProperty({ required: false }) - description?: string - - @IsString() - @ApiProperty({ required: true }) - sku_id: string - - @IsString() - @ApiProperty() - local_sku: string - - @IsEnum(GoodPricingModel) - @ApiProperty({ required: true, enum: GoodPricingModel }) - pricing_model: GoodPricingModel - - @IsOptional() - @IsString() - @ApiProperty({ required: false }) - barcode?: string - - @IsOptional() - @IsString() - @ApiProperty({ required: false }) - category_id?: string - - @IsOptional() - @IsNumber() - @ApiProperty({ required: false }) - base_sale_price?: number - - @IsString() - @ApiProperty({ required: true }) - measure_unit_id: string -} +export class CreateGoodDto extends CreateGuildGoodDto {} export class UpdateGoodDto extends PartialType(CreateGoodDto) {} diff --git a/src/modules/pos/goods/dto/goods-response.dto.ts b/src/modules/pos/goods/dto/goods-response.dto.ts index 619425c..c287f92 100644 --- a/src/modules/pos/goods/dto/goods-response.dto.ts +++ b/src/modules/pos/goods/dto/goods-response.dto.ts @@ -1,5 +1,4 @@ import type { GoodsService } from '../goods.service' -export type GoodsServiceCreateResponseDto = Awaited> export type GoodsServiceFindAllResponseDto = Awaited> export type GoodsServiceFindOneResponseDto = Awaited> diff --git a/src/modules/pos/favorite/favorite.controller.ts b/src/modules/pos/goods/favorite/favorite.controller.ts similarity index 100% rename from src/modules/pos/favorite/favorite.controller.ts rename to src/modules/pos/goods/favorite/favorite.controller.ts diff --git a/src/modules/pos/favorite/favorite.module.ts b/src/modules/pos/goods/favorite/favorite.module.ts similarity index 100% rename from src/modules/pos/favorite/favorite.module.ts rename to src/modules/pos/goods/favorite/favorite.module.ts diff --git a/src/modules/pos/favorite/favorite.service.ts b/src/modules/pos/goods/favorite/favorite.service.ts similarity index 78% rename from src/modules/pos/favorite/favorite.service.ts rename to src/modules/pos/goods/favorite/favorite.service.ts index 80d2356..ccb75d5 100644 --- a/src/modules/pos/favorite/favorite.service.ts +++ b/src/modules/pos/goods/favorite/favorite.service.ts @@ -1,8 +1,8 @@ import { RedisService } from '@/redis/redis.service' import { Injectable } from '@nestjs/common' -import { ResponseMapper } from '../../../common/response/response-mapper' -import { PrismaService } from '../../../prisma/prisma.service' -import { PosCacheInvalidationService } from '../cache/pos-cache-invalidation.service' +import { ResponseMapper } from '../../../../common/response/response-mapper' +import { PrismaService } from '../../../../prisma/prisma.service' +import { PosCacheInvalidationService } from '../../cache/pos-cache-invalidation.service' @Injectable() export class PosGoodFavoriteService { @@ -45,9 +45,7 @@ export class PosGoodFavoriteService { }, }) - await this.posCacheInvalidationService.invalidateGoodsListByBusinessActivity( - business_id, - ) + await this.posCacheInvalidationService.invalidatePosGoodsList(guild_id, business_id) return ResponseMapper.create(favorite) } @@ -79,9 +77,7 @@ export class PosGoodFavoriteService { }, }) - await this.posCacheInvalidationService.invalidateGoodsListByBusinessActivity( - business_id, - ) + await this.posCacheInvalidationService.invalidatePosGoodsList(guild_id, business_id) return ResponseMapper.delete() } diff --git a/src/modules/pos/goods/goods.controller.ts b/src/modules/pos/goods/goods.controller.ts index 79b1b0e..0bf7379 100644 --- a/src/modules/pos/goods/goods.controller.ts +++ b/src/modules/pos/goods/goods.controller.ts @@ -21,7 +21,7 @@ export class GoodsController { } // @Post() - // create(@Body() data: CreateGoodDto, @PosInfo() { complex_id, guild_id }: IPosPayload) { - // return this.goodsService.create(data, complex_id, guild_id) + // create(@Body() data: CreateGoodDto, @PosInfo('complex_id') complex_id: string) { + // return this.goodsService.create(data, complex_id) // } } diff --git a/src/modules/pos/goods/goods.module.ts b/src/modules/pos/goods/goods.module.ts index 7d3d7e7..7e55394 100644 --- a/src/modules/pos/goods/goods.module.ts +++ b/src/modules/pos/goods/goods.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common' -import { PosGoodFavoriteModule } from '../favorite/favorite.module' +import { PosGoodFavoriteModule } from './favorite/favorite.module' import { GoodsController } from './goods.controller' import { GoodsService } from './goods.service' diff --git a/src/modules/pos/goods/goods.service.ts b/src/modules/pos/goods/goods.service.ts index 31a3225..9182ddd 100644 --- a/src/modules/pos/goods/goods.service.ts +++ b/src/modules/pos/goods/goods.service.ts @@ -1,19 +1,15 @@ +import { ResponseMapper } from '@/common/response/response-mapper' import { RedisKeyMaker } from '@/common/utils/redisKeyMaker' import { GoodSelect } from '@/generated/prisma/models' -import { PosCacheInvalidationService } from '@/modules/pos/cache/pos-cache-invalidation.service' +import { PrismaService } from '@/prisma/prisma.service' import { RedisService } from '@/redis/redis.service' import { Injectable } from '@nestjs/common' -import { ResponseMapper } from '../../../common/response/response-mapper' -import { PrismaService } from '../../../prisma/prisma.service' -import { CreateGoodDto } from './dto/create-good.dto' -import { UpdateGoodDto } from './dto/update-good.dto' @Injectable() export class GoodsService { constructor( private prisma: PrismaService, private readonly redisService: RedisService, - private readonly posCacheInvalidationService: PosCacheInvalidationService, ) {} private readonly listCacheTtlSeconds = 300 @@ -56,44 +52,47 @@ export class GoodsService { guild_id: string, consumer_account_id: string, ) { - const cacheKey = RedisKeyMaker.posGoodsList(business_activity_id, guild_id) - const cached = await this.redisService.getJson(cacheKey) - if (cached) { - return ResponseMapper.list(cached) - } - - const goods = await this.prisma.good.findMany({ - where: { - OR: [ - { - is_default_guild_good: true, - category: { - guild_id, + const cacheKey = RedisKeyMaker.posGoodsList(guild_id, business_activity_id) + try { + const cached = await this.redisService.getJson(cacheKey) + if (cached) { + return ResponseMapper.list(cached) + } + throw new Error('Cache miss') + } catch (error) { + const goods = await this.prisma.good.findMany({ + where: { + OR: [ + { + is_default_guild_good: true, + category: { + guild_id, + }, + }, + { + business_activity_id, + }, + ], + }, + select: { + ...this.defaultSelect, + consumer_account_good_favorites: { + where: { + consumer_account_id, }, }, - { - business_activity_id, - }, - ], - }, - select: { - ...this.defaultSelect, - consumer_account_good_favorites: { - where: { - consumer_account_id, - }, }, - }, - }) + }) - const mappedGoods = goods.map(good => ({ - ...good, - is_favorite: good.consumer_account_good_favorites.length > 0, - })) + const mappedGoods = goods.map(good => ({ + ...good, + is_favorite: good.consumer_account_good_favorites.length > 0, + })) - await this.redisService.setJson(cacheKey, mappedGoods, this.listCacheTtlSeconds) + await this.redisService.setJson(cacheKey, mappedGoods, this.listCacheTtlSeconds) - return ResponseMapper.list(mappedGoods) + return ResponseMapper.list(mappedGoods) + } } async findOne(good_id: string, business_activity_id: string, guild_id: string) { @@ -111,113 +110,99 @@ export class GoodsService { return ResponseMapper.single(good) } - async create(data: CreateGoodDto, business_activity_id: string) { - const { category_id, sku_id, measure_unit_id, ...rest } = data + // async create(data: CreateGoodDto, business_activity_id: string, guild_id: string) { + // const { category_id, sku_id, measure_unit_id, ...rest } = data - const dataToCreate = { - ...rest, - business_activity_id, - connect: { - category: { - id: category_id, - }, - }, - } + // const dataToCreate = { + // ...rest, + // measure_unit: { + // connect: { + // id: measure_unit_id, + // }, + // }, + // sku: { + // connect: { + // id: sku_id, + // }, + // }, + // category: { + // connect: { + // id: category_id, + // }, + // }, + // business_activity: { + // connect: { + // id: business_activity_id, + // }, + // }, + // } - const good = await this.prisma.good.create({ - data: { - ...rest, - measure_unit: { - connect: { - id: measure_unit_id, - }, - }, - sku: { - connect: { - id: sku_id, - }, - }, - category: { - connect: { - id: category_id, - }, - }, - business_activity: { - connect: { - id: business_activity_id, - }, - }, - }, - select: this.defaultSelect, - }) + // const good = await this.prisma.good.create({ + // data: { + // ...dataToCreate, + // }, + // select: this.defaultSelect, + // }) - await this.posCacheInvalidationService.invalidateGoodsListByBusinessActivity( - business_activity_id, - ) + // await this.posCacheInvalidationService.invalidatePosGoodsList( + // guild_id, + // business_activity_id, + // ) - return ResponseMapper.create(good) - } + // return ResponseMapper.create(good) + // } - async update(id: string, data: UpdateGoodDto, business_activity_id: string) { - const { category_id, sku_id, measure_unit_id, ...rest } = data + // async update( + // id: string, + // data: UpdateGoodDto, + // business_activity_id: string, + // guild_id: string, + // ) { + // const { category_id, sku_id, measure_unit_id, ...rest } = data - const good = await this.prisma.good.update({ - where: { - id, - business_activity_id, - }, - data: { - ...rest, - ...(measure_unit_id - ? { - measure_unit: { - connect: { - id: measure_unit_id, - }, - }, - } - : {}), - ...(sku_id - ? { - sku: { - connect: { - id: sku_id, - }, - }, - } - : {}), - ...(category_id - ? { - category: { - connect: { - id: category_id, - }, - }, - } - : {}), - }, - select: this.defaultSelect, - }) + // const good = await this.prisma.good.update({ + // where: { + // id, + // business_activity_id, + // }, + // data: { + // ...rest, + // ...(measure_unit_id + // ? { + // measure_unit: { + // connect: { + // id: measure_unit_id, + // }, + // }, + // } + // : {}), + // ...(sku_id + // ? { + // sku: { + // connect: { + // id: sku_id, + // }, + // }, + // } + // : {}), + // ...(category_id + // ? { + // category: { + // connect: { + // id: category_id, + // }, + // }, + // } + // : {}), + // }, + // select: this.defaultSelect, + // }) - await this.posCacheInvalidationService.invalidateGoodsListByBusinessActivity( - business_activity_id, - ) + // await this.posCacheInvalidationService.invalidatePosGoodsList( + // guild_id, + // business_activity_id, + // ) - return ResponseMapper.update(good) - } - - async delete(id: string, business_activity_id: string) { - await this.prisma.good.delete({ - where: { - id, - business_activity_id, - }, - }) - - await this.posCacheInvalidationService.invalidateGoodsListByBusinessActivity( - business_activity_id, - ) - - return ResponseMapper.delete() - } + // return ResponseMapper.update(good) + // } } diff --git a/src/modules/pos/owned-goods/dto/create-owned-goods.dto.ts b/src/modules/pos/owned-goods/dto/create-owned-goods.dto.ts new file mode 100644 index 0000000..bdfa835 --- /dev/null +++ b/src/modules/pos/owned-goods/dto/create-owned-goods.dto.ts @@ -0,0 +1,3 @@ +import { CreateGuildGoodDto } from '@/modules/admin/guilds/goods/dto/create-good.dto' + +export class CreateOwnedGoodsDto extends CreateGuildGoodDto {} diff --git a/src/modules/pos/owned-goods/dto/update-owned-goods.dto.ts b/src/modules/pos/owned-goods/dto/update-owned-goods.dto.ts new file mode 100644 index 0000000..5aa9ea2 --- /dev/null +++ b/src/modules/pos/owned-goods/dto/update-owned-goods.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger' +import { CreateOwnedGoodsDto } from './create-owned-goods.dto' + +export class UpdateOwnedGoodsDto extends PartialType(CreateOwnedGoodsDto) {} diff --git a/src/modules/pos/owned-goods/owned-goods.controller.ts b/src/modules/pos/owned-goods/owned-goods.controller.ts new file mode 100644 index 0000000..dc3615e --- /dev/null +++ b/src/modules/pos/owned-goods/owned-goods.controller.ts @@ -0,0 +1,72 @@ +import { PosInfo } from '@/common/decorators/posInfo.decorator' +import type { IPosPayload } from '@/common/models' +import { multerImageOptions } from '@/multer.config' +import { + Body, + Controller, + Get, + Param, + Patch, + Post, + UploadedFile, + UseInterceptors, +} from '@nestjs/common' +import { FileInterceptor } from '@nestjs/platform-express' +import { ApiBody, ApiConsumes } from '@nestjs/swagger' +import { CreateOwnedGoodsDto } from './dto/create-owned-goods.dto' +import { UpdateOwnedGoodsDto } from './dto/update-owned-goods.dto' +import { OwnedGoodsService } from './owned-goods.service' + +@Controller('pos/owned-goods') +export class OwnedGoodsController { + constructor(private readonly service: OwnedGoodsService) {} + + @Get() + findAll(@PosInfo('business_id') business_id: string) { + return this.service.findAll(business_id) + } + + @Get(':id') + findOne(@PosInfo('business_id') business_id: string, @Param('id') id: string) { + return this.service.findOne(business_id, id) + } + + @Post() + @UseInterceptors(FileInterceptor('image', multerImageOptions)) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + image: { type: 'string', format: 'binary' }, + }, + }, + }) + create( + @PosInfo() { business_id, guild_id }: IPosPayload, + @UploadedFile() file: Express.Multer.File, + @Body() dto: CreateOwnedGoodsDto, + ) { + return this.service.create(guild_id, business_id, dto, file) + } + + @Patch(':id') + @UseInterceptors(FileInterceptor('image', multerImageOptions)) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + image: { type: 'string', format: 'binary' }, + }, + }, + }) + update( + @PosInfo() { business_id, guild_id }: IPosPayload, + @Param('id') id: string, + @UploadedFile() file: Express.Multer.File, + @Body() dto: UpdateOwnedGoodsDto, + ) { + return this.service.update(guild_id, business_id, id, dto, file) + } +} diff --git a/src/modules/pos/owned-goods/owned-goods.module.ts b/src/modules/pos/owned-goods/owned-goods.module.ts new file mode 100644 index 0000000..ea7411f --- /dev/null +++ b/src/modules/pos/owned-goods/owned-goods.module.ts @@ -0,0 +1,12 @@ +import { UploaderModule } from '@/modules/uploader/uploader.module' +import { PrismaService } from '@/prisma/prisma.service' +import { Module } from '@nestjs/common' +import { OwnedGoodsController } from './owned-goods.controller' +import { OwnedGoodsService } from './owned-goods.service' + +@Module({ + controllers: [OwnedGoodsController], + providers: [OwnedGoodsService, PrismaService], + imports: [UploaderModule], +}) +export class OwnedGoodsModule {} diff --git a/src/modules/pos/owned-goods/owned-goods.service.ts b/src/modules/pos/owned-goods/owned-goods.service.ts new file mode 100644 index 0000000..db66f40 --- /dev/null +++ b/src/modules/pos/owned-goods/owned-goods.service.ts @@ -0,0 +1,191 @@ +import { UploadedFileTypes } from '@/common/enums/enums' +import { ResponseMapper } from '@/common/response/response-mapper' +import { GoodSelect, GoodWhereInput } from '@/generated/prisma/models' +import { UploaderService } from '@/modules/uploader/uploader.service' +import { PrismaService } from '@/prisma/prisma.service' +import { Injectable } from '@nestjs/common' +import { PosCacheInvalidationService } from '../cache/pos-cache-invalidation.service' +import { CreateOwnedGoodsDto } from './dto/create-owned-goods.dto' +import { UpdateOwnedGoodsDto } from './dto/update-owned-goods.dto' + +@Injectable() +export class OwnedGoodsService { + constructor( + private readonly prisma: PrismaService, + private readonly uploaderService: UploaderService, + private readonly posCacheInvalidationService: PosCacheInvalidationService, + ) {} + + private readonly where = (business_activity_id: string): GoodWhereInput => ({ + is_default_guild_good: false, + business_activity_id, + }) + + private readonly select: GoodSelect = { + id: true, + name: true, + pricing_model: true, + image_url: true, + description: true, + sku: { + select: { + id: true, + name: true, + code: true, + }, + }, + measure_unit: { + select: { + id: true, + name: true, + code: true, + }, + }, + category: { + select: { + id: true, + name: true, + }, + }, + } + + async findAll(business_activity_id: string) { + const items = await this.prisma.good.findMany({ + where: this.where(business_activity_id), + select: this.select, + }) + + return ResponseMapper.list(items) + } + + async findOne(business_activity_id: string, id: string) { + const item = await this.prisma.good.findUnique({ + where: { ...(this.where(business_activity_id) as any), id }, + select: this.select, + }) + return ResponseMapper.single(item) + } + + async create( + guild_id: string, + business_activity_id: string, + data: CreateOwnedGoodsDto, + file: Express.Multer.File, + ) { + const { category_id, measure_unit_id, sku_id, ...rest } = data + + const good = await this.prisma.$transaction(async tx => { + let image_url = '' + if (file) { + const uploadedUrl = await this.uploaderService.uploadFile( + file, + UploadedFileTypes.GOOD, + ) + image_url = uploadedUrl || '' + } + + return await tx.good.create({ + data: { + ...rest, + is_default_guild_good: true, + image_url, + sku: { + connect: { + id: sku_id, + }, + }, + measure_unit: { + connect: { + id: measure_unit_id, + }, + }, + category: { + connect: { + id: category_id, + }, + }, + business_activity: { + connect: { + id: business_activity_id, + }, + }, + }, + select: this.select, + }) + }) + + await this.posCacheInvalidationService.invalidatePosGoodsList( + guild_id, + business_activity_id, + ) + + return ResponseMapper.create(good) + } + + async update( + guild_id: string, + business_activity_id: string, + id: string, + data: UpdateOwnedGoodsDto, + file: Express.Multer.File, + ) { + const { category_id, measure_unit_id, sku_id, ...rest } = data + + const good = await this.prisma.$transaction(async tx => { + let image_url = undefined as string | undefined + if (file) { + const uploadedUrl = await this.uploaderService.uploadFile( + file, + UploadedFileTypes.GOOD, + ) + image_url = uploadedUrl + } + + const prevImage = await tx.good.findUnique({ + where: { id }, + select: { image_url: true }, + }) + + if (image_url && prevImage?.image_url) { + this.uploaderService.deleteFile(prevImage.image_url, UploadedFileTypes.GOOD) + } + + return await tx.good.update({ + where: { ...(this.where(business_activity_id) as any), id }, + data: { + ...rest, + is_default_guild_good: false, + image_url, + sku: { + connect: { + id: sku_id, + }, + }, + measure_unit: { + connect: { + id: measure_unit_id, + }, + }, + category: { + connect: { + id: category_id, + }, + }, + business_activity: { + connect: { + id: business_activity_id, + }, + }, + }, + select: this.select, + }) + }) + + await this.posCacheInvalidationService.invalidatePosGoodsList( + guild_id, + business_activity_id, + ) + + return ResponseMapper.create(good) + } +} diff --git a/src/modules/pos/pos.module.ts b/src/modules/pos/pos.module.ts index 86c9808..56d3b76 100644 --- a/src/modules/pos/pos.module.ts +++ b/src/modules/pos/pos.module.ts @@ -3,6 +3,7 @@ import { JwtService } from '@nestjs/jwt' import { PosCustomersModule } from './customers/customers.module' import { PosGoodCategoriesModule } from './good-categories/good-categories.module' import { PosGoodsModule } from './goods/goods.module' +import { OwnedGoodsModule } from './owned-goods/owned-goods.module' import { PosController } from './pos.controller' import { PosMiddleware } from './pos.middleware' import { PosService } from './pos.service' @@ -16,6 +17,7 @@ import { PosSalesInvoicesModule } from './sales-invoices/sales-invoices.module' PosGoodCategoriesModule, PosGoodsModule, PosSalesInvoicesModule, + OwnedGoodsModule, ], }) export class PosModule implements NestModule {