refactor(goods): streamline goods service and controller, remove favorites module

- Removed the PosGoodFavorite module, controller, and service to simplify the codebase.
- Updated goods service to handle cache invalidation directly.
- Refactored goods controller to remove commented-out code and improve clarity.
- Introduced OwnedGoods module for better organization of owned goods functionality.
- Updated DTOs to extend from existing structures for consistency.
- Enhanced cache invalidation logic in goods service and owned goods service.
This commit is contained in:
2026-05-20 20:22:00 +03:30
parent fc27b9d616
commit 1d47fb1a1d
31 changed files with 1166 additions and 345 deletions
+5 -3
View File
@@ -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",
+46
View File
@@ -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);
}
}
+9
View File
@@ -0,0 +1,9 @@
import { IsString, IsNumber, IsBoolean, IsEmail } from 'class-validator';
export class Create{{pascalCase name}}Dto {
{{#each parsedFields}}
@{{validator}}()
{{name}}: {{type}};
{{/each}}
}
+10
View File
@@ -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 {}
+63
View File
@@ -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 },
});
}
}
+4
View File
@@ -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) {}
+125
View File
@@ -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',
},
]
},
})
}
+429 -1
View File
@@ -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
-80
View File
@@ -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
// }
}
}
+3 -3
View File
@@ -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
+8 -4
View File
@@ -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:*`
}
}
@@ -12,6 +12,6 @@ export class AdminGuildCacheInvalidationService {
async invalidateGoodsList(guildId: string): Promise<void> {
await this.redisService.delete(RedisKeyMaker.guildGoodsList(guildId))
await this.posCacheInvalidationService.invalidateGoodsListByGuild(guildId)
await this.posCacheInvalidationService.invalidatePosGoodsByGuildPattern(guildId)
}
}
@@ -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)
}
}
+10 -31
View File
@@ -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<string>()
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)
}
@@ -36,7 +36,6 @@ export class ConsumerBusinessActivityGoodsController {
async findOne(
@TokenAccount('userId') consumer_id: string,
@Param('businessActivityId') businessActivityId: string,
@Param('id') id: string,
): Promise<ConsumerBusinessActivityGoodsServiceFindOneResponseDto> {
return this.service.findOne(consumer_id, businessActivityId, id)
@@ -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)
}
+9 -15
View File
@@ -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<void> {
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<void> {
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<void> {
this.redisService.deleteByPattern(PosKeyMaker.goodListByGuildPattern(guildId))
}
}
+3 -44
View File
@@ -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) {}
@@ -1,5 +1,4 @@
import type { GoodsService } from '../goods.service'
export type GoodsServiceCreateResponseDto = Awaited<ReturnType<GoodsService['create']>>
export type GoodsServiceFindAllResponseDto = Awaited<ReturnType<GoodsService['findAll']>>
export type GoodsServiceFindOneResponseDto = Awaited<ReturnType<GoodsService['findOne']>>
@@ -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()
}
+2 -2
View File
@@ -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)
// }
}
+1 -1
View File
@@ -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'
+124 -139
View File
@@ -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<unknown[]>(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<unknown[]>(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)
// }
}
@@ -0,0 +1,3 @@
import { CreateGuildGoodDto } from '@/modules/admin/guilds/goods/dto/create-good.dto'
export class CreateOwnedGoodsDto extends CreateGuildGoodDto {}
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger'
import { CreateOwnedGoodsDto } from './create-owned-goods.dto'
export class UpdateOwnedGoodsDto extends PartialType(CreateOwnedGoodsDto) {}
@@ -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)
}
}
@@ -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 {}
@@ -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)
}
}
+2
View File
@@ -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 {