consumer pos
This commit is contained in:
@@ -17,3 +17,9 @@ DB_ROOT_PASSWORD="root_password"
|
|||||||
NODE_ENV="production"
|
NODE_ENV="production"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ARVANCLOUD_ENDPOINT=https://s3.ir-thr-at1.arvanstorage.com
|
||||||
|
ARVANCLOUD_ACCESS_KEY_ID=YOUR_ACCESS_KEY
|
||||||
|
ARVANCLOUD_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
|
||||||
|
ARVANCLOUD_BUCKET=your-bucket-name
|
||||||
|
ARVANCLOUD_REGION=ir-thr-at1
|
||||||
|
|||||||
Vendored
+1
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"ARVANCLOUD",
|
||||||
"autoincrement",
|
"autoincrement",
|
||||||
"Cardex",
|
"Cardex",
|
||||||
"fkey",
|
"fkey",
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.1029.0",
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/config": "^4.0.4",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
"@nestjs/jwt": "^11.0.2",
|
"@nestjs/jwt": "^11.0.2",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
|
|||||||
Generated
+1372
-54
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -11,7 +11,7 @@ import { PosModule } from './modules/pos/pos.module'
|
|||||||
import { SalesInvoiceItemsModule } from './modules/pos/sales-invoices/sales-invoice-items/sales-invoice-items.module'
|
import { SalesInvoiceItemsModule } from './modules/pos/sales-invoices/sales-invoice-items/sales-invoice-items.module'
|
||||||
import { SalesInvoicePaymentsModule } from './modules/pos/sales-invoices/sales-invoice-payments/sales-invoice-payments.module'
|
import { SalesInvoicePaymentsModule } from './modules/pos/sales-invoices/sales-invoice-payments/sales-invoice-payments.module'
|
||||||
import { TriggerLogsModule } from './modules/trigger-logs/trigger-logs.module'
|
import { TriggerLogsModule } from './modules/trigger-logs/trigger-logs.module'
|
||||||
import { UploadersModule } from './modules/uploaders/uploaders.module'
|
import { UploaderModule } from './modules/uploaders/uploader.module'
|
||||||
import { PrismaModule } from './prisma/prisma.module'
|
import { PrismaModule } from './prisma/prisma.module'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -27,7 +27,7 @@ import { PrismaModule } from './prisma/prisma.module'
|
|||||||
SalesInvoiceItemsModule,
|
SalesInvoiceItemsModule,
|
||||||
SalesInvoicePaymentsModule,
|
SalesInvoicePaymentsModule,
|
||||||
TriggerLogsModule,
|
TriggerLogsModule,
|
||||||
UploadersModule,
|
UploaderModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './consumerPayload.model'
|
export * from './consumerPayload.model'
|
||||||
export * from './posPayload.model'
|
export * from './posPayload.model'
|
||||||
export * from './tokenPayload.model'
|
export * from './tokenPayload.model'
|
||||||
|
export * from './uploadedFileTypes.model'
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export type TUploadedFileTypes = 'good' | 'service' | 'profile_avatar'
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import * as pos from './pos'
|
||||||
|
|
||||||
|
export const QUERY_CONSTANTS = {
|
||||||
|
pos,
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { PosSelect } from '@/generated/prisma/models'
|
||||||
|
|
||||||
|
export const select: PosSelect = {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
model: true,
|
||||||
|
serial: true,
|
||||||
|
status: true,
|
||||||
|
created_at: true,
|
||||||
|
pos_type: true,
|
||||||
|
|
||||||
|
provider: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
id: true,
|
||||||
|
brand: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
complex: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
business_activity: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { ConsumerController } from './consumer.controller'
|
|||||||
import { ConsumerMiddleware } from './consumer.middleware'
|
import { ConsumerMiddleware } from './consumer.middleware'
|
||||||
import { ConsumerService } from './consumer.service'
|
import { ConsumerService } from './consumer.service'
|
||||||
import { ConsumerCustomersModule } from './customers/customers.module'
|
import { ConsumerCustomersModule } from './customers/customers.module'
|
||||||
|
import { ConsumerPosesModule } from './poses/poses.module'
|
||||||
import { ConsumerStatisticsModule } from './statistics/statistics.module'
|
import { ConsumerStatisticsModule } from './statistics/statistics.module'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -15,6 +16,7 @@ import { ConsumerStatisticsModule } from './statistics/statistics.module'
|
|||||||
ConsumerBusinessActivitiesModule,
|
ConsumerBusinessActivitiesModule,
|
||||||
ConsumerCustomersModule,
|
ConsumerCustomersModule,
|
||||||
ConsumerStatisticsModule,
|
ConsumerStatisticsModule,
|
||||||
|
ConsumerPosesModule,
|
||||||
],
|
],
|
||||||
providers: [JwtService, ConsumerService],
|
providers: [JwtService, ConsumerService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { ApiProperty, PartialType } from '@nestjs/swagger'
|
||||||
|
import { IsString } from 'class-validator'
|
||||||
|
|
||||||
|
export class CreateBusinessActivityDto {
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty({ required: true })
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateBusinessActivityDto extends PartialType(CreateBusinessActivityDto) {}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { TokenAccount } from '@/common/decorators/tokenInfo.decorator'
|
||||||
|
import { Body, Controller, Get, Param, Patch } from '@nestjs/common'
|
||||||
|
import { UpdateBusinessActivityDto } from './dto/poses.dto'
|
||||||
|
import { PosesService } from './poses.service'
|
||||||
|
|
||||||
|
@Controller('consumer/poses')
|
||||||
|
export class PosesController {
|
||||||
|
constructor(private readonly service: PosesService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findAll(@TokenAccount('userId') userId: string) {
|
||||||
|
return this.service.findAll(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@TokenAccount('userId') userId: string, @Param('id') id: string) {
|
||||||
|
return this.service.findOne(userId, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
async update(
|
||||||
|
@TokenAccount('userId') userId: string,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() data: UpdateBusinessActivityDto,
|
||||||
|
) {
|
||||||
|
return this.service.update(userId, id, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { PrismaModule } from '@/prisma/prisma.module'
|
||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { PosesController } from './poses.controller'
|
||||||
|
import { PosesService } from './poses.service'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [PosesController],
|
||||||
|
providers: [PosesService],
|
||||||
|
})
|
||||||
|
export class ConsumerPosesModule {}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { QUERY_CONSTANTS } from '@/common/queryConstants'
|
||||||
|
import { PrismaService } from '@/prisma/prisma.service'
|
||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
import { ResponseMapper } from 'common/response/response-mapper'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PosesService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
private readonly defaultSelect = QUERY_CONSTANTS.pos.select
|
||||||
|
private readonly defaultWhere = (consumer_id: string) => ({
|
||||||
|
complex: {
|
||||||
|
business_activity: {
|
||||||
|
consumer_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
async findAll(consumer_id: string) {
|
||||||
|
const poses = await this.prisma.pos.findMany({
|
||||||
|
where: this.defaultWhere(consumer_id),
|
||||||
|
select: this.defaultSelect,
|
||||||
|
})
|
||||||
|
return ResponseMapper.list(poses)
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(consumer_id: string, id: string) {
|
||||||
|
const pos = await this.prisma.pos.findUniqueOrThrow({
|
||||||
|
where: { ...this.defaultWhere(consumer_id), id },
|
||||||
|
select: this.defaultSelect,
|
||||||
|
})
|
||||||
|
return ResponseMapper.single(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(consumer_id: string, id: string, data: any) {
|
||||||
|
const pos = await this.prisma.pos.update({
|
||||||
|
where: { ...this.defaultWhere(consumer_id), id },
|
||||||
|
data,
|
||||||
|
select: this.defaultSelect,
|
||||||
|
})
|
||||||
|
return ResponseMapper.update(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { ConfigModule } from '@nestjs/config'
|
||||||
|
import { StorageService } from './storage.service'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule.forRoot()],
|
||||||
|
providers: [StorageService],
|
||||||
|
exports: [StorageService],
|
||||||
|
})
|
||||||
|
export class StorageModule {}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { DeleteObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import { randomUUID } from 'crypto'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StorageService {
|
||||||
|
private readonly s3: S3Client
|
||||||
|
private readonly bucket: string
|
||||||
|
private readonly endpoint: string
|
||||||
|
|
||||||
|
constructor(private readonly config: ConfigService) {
|
||||||
|
;((this.endpoint = process.env.ARVANCLOUD_ENDPOINT!),
|
||||||
|
(this.bucket = process.env.ARVANCLOUD_BUCKET!),
|
||||||
|
(this.s3 = new S3Client({
|
||||||
|
region: process.env.ARVANCLOUD_REGION!,
|
||||||
|
endpoint: this.endpoint!,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.ARVANCLOUD_ACCESS_KEY_ID!,
|
||||||
|
secretAccessKey: process.env.ARVANCLOUD_SECRET_ACCESS_KEY!,
|
||||||
|
},
|
||||||
|
forcePathStyle: true,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFile(file: Express.Multer.File, folder = 'uploads') {
|
||||||
|
const key = `${folder}/${randomUUID()}-${file.originalname}`
|
||||||
|
await this.s3.send(
|
||||||
|
new PutObjectCommand({
|
||||||
|
Bucket: this.bucket,
|
||||||
|
Key: key,
|
||||||
|
Body: file.buffer,
|
||||||
|
ContentType: file.mimetype,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ArvanCloud uses public URLs like:
|
||||||
|
const publicUrl = `${this.endpoint}/${this.bucket}/${key}`
|
||||||
|
return { key, url: publicUrl }
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFile(key: string) {
|
||||||
|
await this.s3.send(
|
||||||
|
new DeleteObjectCommand({
|
||||||
|
Bucket: this.bucket,
|
||||||
|
Key: key,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { TUploadedFileTypes } from '@/common/models'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { UploaderService } from './uploader.service'
|
import { UploaderService } from './uploader.service'
|
||||||
|
|
||||||
@@ -7,14 +8,8 @@ export class ImageUploaderService {
|
|||||||
|
|
||||||
async uploadImage(
|
async uploadImage(
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
account_id: string,
|
type: TUploadedFileTypes,
|
||||||
type: string,
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return this.uploaderService.uploadFile(
|
return this.uploaderService.uploadFile(file.buffer, file.originalname, type)
|
||||||
file.buffer,
|
|
||||||
file.originalname,
|
|
||||||
account_id,
|
|
||||||
type,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -1,9 +1,10 @@
|
|||||||
|
import type { TUploadedFileTypes } from '@/common/models'
|
||||||
import { Body, Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common'
|
import { Body, Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common'
|
||||||
import { FileInterceptor } from '@nestjs/platform-express'
|
import { FileInterceptor } from '@nestjs/platform-express'
|
||||||
import { ImageUploaderService } from './image-uploader.service'
|
import { ImageUploaderService } from './image-uploader.service'
|
||||||
|
|
||||||
@Controller('uploaders')
|
@Controller('uploader')
|
||||||
export class UploadersController {
|
export class UploaderController {
|
||||||
constructor(private readonly imageUploaderService: ImageUploaderService) {}
|
constructor(private readonly imageUploaderService: ImageUploaderService) {}
|
||||||
|
|
||||||
@Post('image')
|
@Post('image')
|
||||||
@@ -11,9 +12,9 @@ export class UploadersController {
|
|||||||
async uploadImage(
|
async uploadImage(
|
||||||
@UploadedFile() file: Express.Multer.File,
|
@UploadedFile() file: Express.Multer.File,
|
||||||
@Body('accountId') accountId: string,
|
@Body('accountId') accountId: string,
|
||||||
@Body('type') type: string,
|
@Body('type') type: TUploadedFileTypes,
|
||||||
) {
|
) {
|
||||||
const url = await this.imageUploaderService.uploadImage(file, accountId, type)
|
const url = await this.imageUploaderService.uploadImage(file, type)
|
||||||
return { url }
|
return { url }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { checkAndDecodeJwtToken } from '@/common/utils/jwt-user.util'
|
||||||
|
import { PrismaService } from '@/prisma/prisma.service'
|
||||||
|
import { ForbiddenException, Injectable, NestMiddleware } from '@nestjs/common'
|
||||||
|
import { JwtService } from '@nestjs/jwt'
|
||||||
|
import { NextFunction, Request, Response } from 'express'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PosMiddleware implements NestMiddleware {
|
||||||
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
|
private jwtService: JwtService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async use(req: Request, _res: Response, next: NextFunction) {
|
||||||
|
const doForbidden = () => {
|
||||||
|
throw new ForbiddenException('شما دسترسی لازم را ندارید')
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const tokenAccount = checkAndDecodeJwtToken(req, this.jwtService)
|
||||||
|
|
||||||
|
const { type, account_id } = tokenAccount!
|
||||||
|
if (type === 'ADMIN' || type === 'CONSUMER') {
|
||||||
|
const account = await this.prisma.account.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
admin_account: {
|
||||||
|
id: account_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
consumer_account: {
|
||||||
|
id: account_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (account) return next()
|
||||||
|
}
|
||||||
|
throw doForbidden()
|
||||||
|
} catch (error) {
|
||||||
|
if (error && typeof error === 'object') {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return doForbidden()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common'
|
import { Module } from '@nestjs/common'
|
||||||
import { ImageUploaderService } from './image-uploader.service'
|
import { ImageUploaderService } from './image-uploader.service'
|
||||||
|
import { UploaderController } from './uploader.controller'
|
||||||
import { UploaderService } from './uploader.service'
|
import { UploaderService } from './uploader.service'
|
||||||
import { UploadersController } from './uploaders.controller'
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [UploaderService, ImageUploaderService],
|
providers: [UploaderService, ImageUploaderService],
|
||||||
controllers: [UploadersController],
|
controllers: [UploaderController],
|
||||||
exports: [UploaderService, ImageUploaderService],
|
exports: [UploaderService, ImageUploaderService],
|
||||||
})
|
})
|
||||||
export class UploadersModule {}
|
export class UploaderModule {}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { TUploadedFileTypes } from '@/common/models'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
@@ -10,16 +11,15 @@ export class UploaderService {
|
|||||||
async uploadFile(
|
async uploadFile(
|
||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
originalname: string,
|
originalname: string,
|
||||||
accountId: string,
|
type: TUploadedFileTypes,
|
||||||
type: string,
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const ext = originalname.split('.').pop()
|
const ext = originalname.split('.').pop()
|
||||||
const filename = `${uuidv4()}.${ext}`
|
const filename = `${uuidv4()}.${ext}`
|
||||||
const dirPath = join(this.uploadDir, accountId, type)
|
const dirPath = join(this.uploadDir, type)
|
||||||
const filePath = join(dirPath, filename)
|
const filePath = join(dirPath, filename)
|
||||||
await fs.mkdir(dirPath, { recursive: true })
|
await fs.mkdir(dirPath, { recursive: true })
|
||||||
await fs.writeFile(filePath, buffer)
|
await fs.writeFile(filePath, buffer)
|
||||||
// Return a URL path, e.g. /uploads/{accountId}/{type}/{filename}
|
// Return a URL path, e.g. /uploads/{accountId}/{type}/{filename}
|
||||||
return `/uploads/${accountId}/${type}/${filename}`
|
return `/uploads/${type}/${filename}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user