consumer pos
This commit is contained in:
@@ -17,3 +17,9 @@ DB_ROOT_PASSWORD="root_password"
|
||||
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,
|
||||
"cSpell.words": [
|
||||
"ARVANCLOUD",
|
||||
"autoincrement",
|
||||
"Cardex",
|
||||
"fkey",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1029.0",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.4",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.2",
|
||||
"@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 { SalesInvoicePaymentsModule } from './modules/pos/sales-invoices/sales-invoice-payments/sales-invoice-payments.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'
|
||||
|
||||
@Module({
|
||||
@@ -27,7 +27,7 @@ import { PrismaModule } from './prisma/prisma.module'
|
||||
SalesInvoiceItemsModule,
|
||||
SalesInvoicePaymentsModule,
|
||||
TriggerLogsModule,
|
||||
UploadersModule,
|
||||
UploaderModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './consumerPayload.model'
|
||||
export * from './posPayload.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 { ConsumerService } from './consumer.service'
|
||||
import { ConsumerCustomersModule } from './customers/customers.module'
|
||||
import { ConsumerPosesModule } from './poses/poses.module'
|
||||
import { ConsumerStatisticsModule } from './statistics/statistics.module'
|
||||
|
||||
@Module({
|
||||
@@ -15,6 +16,7 @@ import { ConsumerStatisticsModule } from './statistics/statistics.module'
|
||||
ConsumerBusinessActivitiesModule,
|
||||
ConsumerCustomersModule,
|
||||
ConsumerStatisticsModule,
|
||||
ConsumerPosesModule,
|
||||
],
|
||||
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 { UploaderService } from './uploader.service'
|
||||
|
||||
@@ -7,14 +8,8 @@ export class ImageUploaderService {
|
||||
|
||||
async uploadImage(
|
||||
file: Express.Multer.File,
|
||||
account_id: string,
|
||||
type: string,
|
||||
type: TUploadedFileTypes,
|
||||
): Promise<string> {
|
||||
return this.uploaderService.uploadFile(
|
||||
file.buffer,
|
||||
file.originalname,
|
||||
account_id,
|
||||
type,
|
||||
)
|
||||
return this.uploaderService.uploadFile(file.buffer, file.originalname, type)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,9 +1,10 @@
|
||||
import type { TUploadedFileTypes } from '@/common/models'
|
||||
import { Body, Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common'
|
||||
import { FileInterceptor } from '@nestjs/platform-express'
|
||||
import { ImageUploaderService } from './image-uploader.service'
|
||||
|
||||
@Controller('uploaders')
|
||||
export class UploadersController {
|
||||
@Controller('uploader')
|
||||
export class UploaderController {
|
||||
constructor(private readonly imageUploaderService: ImageUploaderService) {}
|
||||
|
||||
@Post('image')
|
||||
@@ -11,9 +12,9 @@ export class UploadersController {
|
||||
async uploadImage(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@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 }
|
||||
}
|
||||
}
|
||||
@@ -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 { ImageUploaderService } from './image-uploader.service'
|
||||
import { UploaderController } from './uploader.controller'
|
||||
import { UploaderService } from './uploader.service'
|
||||
import { UploadersController } from './uploaders.controller'
|
||||
|
||||
@Module({
|
||||
providers: [UploaderService, ImageUploaderService],
|
||||
controllers: [UploadersController],
|
||||
controllers: [UploaderController],
|
||||
exports: [UploaderService, ImageUploaderService],
|
||||
})
|
||||
export class UploadersModule {}
|
||||
export class UploaderModule {}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { TUploadedFileTypes } from '@/common/models'
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { promises as fs } from 'fs'
|
||||
import { join } from 'path'
|
||||
@@ -10,16 +11,15 @@ export class UploaderService {
|
||||
async uploadFile(
|
||||
buffer: Buffer,
|
||||
originalname: string,
|
||||
accountId: string,
|
||||
type: string,
|
||||
type: TUploadedFileTypes,
|
||||
): Promise<string> {
|
||||
const ext = originalname.split('.').pop()
|
||||
const filename = `${uuidv4()}.${ext}`
|
||||
const dirPath = join(this.uploadDir, accountId, type)
|
||||
const dirPath = join(this.uploadDir, type)
|
||||
const filePath = join(dirPath, filename)
|
||||
await fs.mkdir(dirPath, { recursive: true })
|
||||
await fs.writeFile(filePath, buffer)
|
||||
// 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