This commit is contained in:
2026-04-22 21:55:40 +03:30
parent 1a3a450960
commit f9e1ad69dc
85 changed files with 15644 additions and 4057 deletions
@@ -0,0 +1,16 @@
import { Public } from '@/common/decorators/public.decorator'
import { Controller, Get } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { AppService } from './application.service'
@ApiTags('App')
@Controller('app')
export class AppController {
constructor(private service: AppService) {}
@Get('/check-version')
@Public()
checkVersion() {
return this.service.checkVersion()
}
}
@@ -0,0 +1,151 @@
import { checkAndDecodeJwtToken } from '@/common/utils/jwt-user.util'
import { ConsumerRole } from '@/generated/prisma/enums'
import { PrismaService } from '@/prisma/prisma.service'
import {
ForbiddenException,
Injectable,
NestMiddleware,
PreconditionFailedException,
} 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, role, account_id } = tokenAccount!
let posId = req.cookies['posId']
if (type !== 'CONSUMER') {
return doForbidden()
}
if (req.url.startsWith('/api/v1/pos/accessible')) {
req.decodedToken = tokenAccount!
return next()
}
if (!posId || typeof posId !== 'string') {
const pos = await this.prisma.pos.findMany({
where: {
complex: {
business_activity: {
consumer: {
consumer_accounts: {
some: {
id: account_id,
},
},
},
},
},
permission_pos: {
some: {
permission: {
consumer_account_id: account_id,
},
},
},
},
})
if (!pos?.length) {
throw new ForbiddenException('پایانه‌ی فروشی برای شما یافت نشد')
}
if (pos.length === 1) {
posId = pos[0].id
} else
throw new PreconditionFailedException('پایانه‌ی مورد نظر خود را انتخاب کنید.')
}
const pos = await this.prisma.pos.findUnique({
where: {
id: posId,
},
select: {
id: true,
complex: {
select: {
id: true,
business_activity: {
select: {
id: true,
guild_id: true,
},
},
},
},
},
})
if (!pos) {
return doForbidden()
}
if (role !== ConsumerRole.OWNER) {
const accountPermissions = await this.prisma.permissionConsumer.findUnique({
where: {
consumer_account_id: account_id,
},
select: {
pos_permissions: {
select: {
pos_id: true,
},
},
business_permissions: {
select: {
business_id: true,
},
},
complex_permissions: {
select: {
complex_id: true,
},
},
},
})
if (
!// accountPermissions?.business_permissions.some(
// permission => permission.id,
// ) ||
// accountPermissions?.complex_permissions.some(
// permission => permission.complex_id,
// ) ||
accountPermissions?.pos_permissions.some(permission => permission.pos_id)
) {
return doForbidden()
}
}
req.posData = {
pos_id: posId,
complex_id: pos.complex.id,
business_id: pos.complex.id,
guild_id: pos.complex.business_activity.guild_id,
consumer_account_id: account_id,
}
req.decodedToken = tokenAccount!
return next()
} catch (error) {
if (error && typeof error === 'object') {
throw error
}
return doForbidden()
}
}
}
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { AppController } from './application.controller'
import { AppService } from './application.service'
import { ApplicationAuthModule } from './auth/auth.module'
import { ApplicationConfigModule } from './config/config.module'
@Module({
controllers: [AppController],
providers: [AppService, JwtService],
imports: [ApplicationConfigModule, ApplicationAuthModule],
})
export class ApplicationModule {}
@@ -0,0 +1,27 @@
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable } from '@nestjs/common'
@Injectable()
export class AppService {
constructor(private readonly prisma: PrismaService) {}
async checkVersion() {
return await this.prisma.applicationReleasedInfo.findFirst({
where: {
is_minimum_supported: true,
},
orderBy: {
build_number: 'desc',
},
select: {
build_number: true,
version: true,
platform: true,
is_minimum_supported: true,
release_type: true,
notes: true,
release_date: true,
},
})
}
}
@@ -0,0 +1,17 @@
import { Public } from '@/common/decorators/public.decorator'
import { Body, Controller, Post } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { ApplicationAuthService } from './auth.service'
import { applicationLoginDto } from './dto/auth.dto'
@ApiTags('Application Auth')
@Controller('app/auth')
export class ApplicationAuthController {
constructor(private service: ApplicationAuthService) {}
@Post('/login')
@Public()
login(@Body() data: applicationLoginDto) {
return this.service.login(data)
}
}
@@ -0,0 +1,11 @@
import { AuthModule } from '@/modules/auth/auth.module'
import { Module } from '@nestjs/common'
import { ApplicationAuthController } from './auth.controller'
import { ApplicationAuthService } from './auth.service'
@Module({
controllers: [ApplicationAuthController],
providers: [ApplicationAuthService],
imports: [AuthModule],
})
export class ApplicationAuthModule {}
@@ -0,0 +1,14 @@
import { AuthService } from '@/modules/auth/auth.service'
import { Injectable } from '@nestjs/common'
import { applicationLoginDto } from './dto/auth.dto'
@Injectable()
export class ApplicationAuthService {
constructor(private readonly authService: AuthService) {}
async login(data: applicationLoginDto) {
const loginResponse = await this.authService.login(data, true)
return loginResponse
}
}
@@ -0,0 +1,3 @@
import { LoginDto } from '@/modules/auth/dto/login.dto'
export class applicationLoginDto extends LoginDto {}
@@ -0,0 +1,19 @@
import { TokenAccount } from '@/common/decorators/tokenInfo.decorator'
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
import { ConfigService } from './config.service'
import { CreateConfigDto } from './dto/create-config.dto'
@Controller('app/config')
export class ConfigController {
constructor(private readonly configService: ConfigService) {}
@Get(':id')
findOne(@Param('id') uuid: string) {
return this.configService.findOne(uuid)
}
@Post()
create(@TokenAccount('userId') consumerId: string, @Body() data: CreateConfigDto) {
return this.configService.create(consumerId, data)
}
}
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common'
import { ConfigController } from './config.controller'
import { ConfigService } from './config.service'
@Module({
controllers: [ConfigController],
providers: [ConfigService],
})
export class ApplicationConfigModule {}
@@ -0,0 +1,83 @@
import { ConsumerDevicesSelect } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { CreateConfigDto } from './dto/create-config.dto'
@Injectable()
export class ConfigService {
constructor(private prisma: PrismaService) {}
private readonly defaultSelect: ConsumerDevicesSelect = {
uuid: true,
app_version: true,
brand: true,
browser_name: true,
build_number: true,
device: true,
os_version: true,
fcm_token: true,
model: true,
platform: true,
release_number: true,
sdk_version: true,
consumer: {
select: {
id: true,
first_name: true,
last_name: true,
},
},
}
async findOne(uuid: string) {
const config = await this.prisma.consumerDevices.findUnique({
where: {
uuid,
},
select: this.defaultSelect,
})
return ResponseMapper.single(config)
}
async create(consumer_id: string, data: CreateConfigDto) {
const prevConfig = await this.prisma.consumerDevices.findUnique({
where: {
uuid: data.uuid,
},
select: {
consumer_id: true,
},
})
if (prevConfig && consumer_id !== prevConfig.consumer_id) {
throw new BadRequestException('این دستگاه با کاربری دیگری ثبت شده است.')
}
const config = await this.prisma.consumerDevices.upsert({
where: {
uuid: data.uuid,
},
update: {
...data,
consumer: {
connect: {
id: consumer_id,
},
},
},
create: {
...data,
consumer: {
connect: {
id: consumer_id,
},
},
},
select: this.defaultSelect,
})
return ResponseMapper.create(config)
}
}
@@ -0,0 +1,66 @@
import { ApplicationPlatform, ApplicationPublisher } from '@/generated/prisma/enums'
import { ApiProperty, PartialType } from '@nestjs/swagger'
import { IsEnum, IsOptional, IsString } from 'class-validator'
export class CreateConfigDto {
@ApiProperty({ required: true })
@IsString()
uuid: string
@ApiProperty({ required: true })
@IsEnum(ApplicationPublisher)
publisher: ApplicationPublisher
@ApiProperty({ required: true })
@IsString()
app_version: string
@ApiProperty({ required: true })
@IsString()
build_number: string
@ApiProperty({ required: true })
@IsEnum(ApplicationPlatform)
platform: ApplicationPlatform
@ApiProperty({ required: true })
@IsString()
brand: string
@ApiProperty({ required: true })
@IsString()
model: string
@ApiProperty({ required: true })
@IsString()
device: string
@ApiProperty({ required: true })
@IsString()
os_version: string
@ApiProperty({ required: true })
@IsString()
sdk_version: string
@ApiProperty({ required: true })
@IsString()
release_number: string
@ApiProperty({ required: false })
@IsString()
@IsOptional()
browser_name: string
@ApiProperty({ required: false })
@IsString()
@IsOptional()
user_agent: string
@ApiProperty({ required: false })
@IsString()
@IsOptional()
fcm_token?: string
}
export class UpdateConfigDto extends PartialType(CreateConfigDto) {}