diff --git a/req.d.ts b/req.d.ts new file mode 100644 index 0000000..47bad14 --- /dev/null +++ b/req.d.ts @@ -0,0 +1,12 @@ +import { AccessTokenPayload } from '@/modules/auth/models' + +declare global { + interface Request { + decodedToken: AccessTokenPayload + } + namespace Express { + interface Request { + decodedToken: AccessTokenPayload + } + } +} diff --git a/src/app.module.ts b/src/app.module.ts index 5cd1c13..9fc9ff1 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { AppService } from './app.service' import { AdminModule } from './modules/admin/admin.module' import { AuthModule } from './modules/auth/auth.module' import { CatalogModule } from './modules/catalog/catalog.module' +import { ConsumerModule } from './modules/consumer/consumer.module' import { EnumsModule } from './modules/enums/enums.module' import { PosModule } from './modules/pos/pos.module' import { SalesInvoiceItemsModule } from './modules/pos/sales-invoices/sales-invoice-items/sales-invoice-items.module' @@ -21,6 +22,7 @@ import { PrismaModule } from './prisma/prisma.module' EnumsModule, AdminModule, CatalogModule, + ConsumerModule, PosModule, AuthModule, SalesInvoiceItemsModule, diff --git a/src/common/decorators/tokenInfo.decorator.ts b/src/common/decorators/tokenInfo.decorator.ts new file mode 100644 index 0000000..6dfc310 --- /dev/null +++ b/src/common/decorators/tokenInfo.decorator.ts @@ -0,0 +1,35 @@ +import { AccessTokenPayload } from '@/modules/auth/models' +import { + BadRequestException, + createParamDecorator, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common' +import { Request } from 'express' + +export const TokenAccount = createParamDecorator( + (data: keyof AccessTokenPayload | 'userId' | undefined, ctx: ExecutionContext) => { + try { + const request = ctx.switchToHttp().getRequest() + const account = request.decodedToken + + // ✅ if middleware didn't populate req.account, return undefined or throw + if (!account.user?.id) { + throw new UnauthorizedException('شما به این بخش دسترسی ندارید') + } + + // ✅ if decorator called with a key (e.g. @account('accountId')) + if (data) { + if (data === 'userId') { + return account.user?.id + } + return account[data] + } + + // ✅ if called with no param — return full account object + return account + } catch (err) { + throw new BadRequestException('مشکلی در ساختار درخواست شما وجود دارد.') + } + }, +) diff --git a/src/common/utils/jwt-user.util.ts b/src/common/utils/jwt-user.util.ts index 9753d08..5f63f48 100644 --- a/src/common/utils/jwt-user.util.ts +++ b/src/common/utils/jwt-user.util.ts @@ -1,11 +1,13 @@ +import { ITokenPayload } from '@/modules/auth/auth.utils' import { UnauthorizedException } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' import { Request } from 'express' import { AccessTokenPayload } from 'modules/auth/models' -export function getAccountIdFromJwt(req: Request, jwtService: JwtService): string | null { - // Try to get token from Authorization header - +export function checkAndDecodeJwtToken( + req: Request, + jwtService: JwtService, +): ITokenPayload | null { let token = req.cookies?.accessToken if (!token) { @@ -18,11 +20,10 @@ export function getAccountIdFromJwt(req: Request, jwtService: JwtService): strin } if (!token) throw new UnauthorizedException('Missing accessToken cookie') - // const token = authHeader.replace('Bearer ', '') try { const payload = jwtService.decode(token) as AccessTokenPayload - return payload?.account_id || null + return payload as ITokenPayload } catch { return null } diff --git a/src/modules/admin/admin.middleware.ts b/src/modules/admin/admin.middleware.ts index 6d6d9a8..d610db0 100644 --- a/src/modules/admin/admin.middleware.ts +++ b/src/modules/admin/admin.middleware.ts @@ -1,20 +1,18 @@ +import { checkAndDecodeJwtToken } from '@/common/utils/jwt-user.util' +import { AccountType } from '@/generated/prisma/enums' import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' import { NextFunction, Request, Response } from 'express' @Injectable() export class AdminMiddleware implements NestMiddleware { + constructor(private jwtService: JwtService) {} use(req: Request, _res: Response, next: NextFunction) { - // const a = reqTokenPayload() - // console.log(a) - - // Middleware-based admin check (replace with real auth logic) - const isAdminHeader = req.headers['x-admin'] - - if (isAdminHeader === 'true') { - // mark request if needed downstream - req['isAdminRequest'] = true + const decodedToken = checkAndDecodeJwtToken(req, this.jwtService) + if (decodedToken?.type === AccountType.ADMIN) { + req.decodedToken = decodedToken + return next() } - return next() throw new UnauthorizedException('Admin access required') } diff --git a/src/modules/admin/admin.module.ts b/src/modules/admin/admin.module.ts index 715b277..7f14685 100644 --- a/src/modules/admin/admin.module.ts +++ b/src/modules/admin/admin.module.ts @@ -1,4 +1,5 @@ import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' import { AdminController } from './admin.controller' import { AdminMiddleware } from './admin.middleware' import { AdminConsumersModule } from './consumers/consumers.module' @@ -27,6 +28,7 @@ import { AdminUsersModule } from './users/users.module' AdminLicensesModule, AdminConsumersModule, ], + providers: [JwtService], }) export class AdminModule implements NestModule { configure(consumer: MiddlewareConsumer) { diff --git a/src/modules/admin/consumers/accounts/accounts.controller.ts b/src/modules/admin/consumers/accounts/accounts.controller.ts index 9953cc7..fad97ab 100644 --- a/src/modules/admin/consumers/accounts/accounts.controller.ts +++ b/src/modules/admin/consumers/accounts/accounts.controller.ts @@ -3,7 +3,7 @@ import { ApiTags } from '@nestjs/swagger' import { AccountsService } from './accounts.service' import { CreateConsumerAccountDto, UpdateAccountDto } from './dto/account.dto' -@ApiTags('ConsumerAccounts') +@ApiTags('AdminConsumerAccounts') @Controller('admin/consumers/:consumerId/accounts') export class AccountsController { constructor(private readonly accountsService: AccountsService) {} diff --git a/src/modules/admin/consumers/business-activities/business-activities.controller.ts b/src/modules/admin/consumers/business-activities/business-activities.controller.ts index d973a0d..3cf619c 100644 --- a/src/modules/admin/consumers/business-activities/business-activities.controller.ts +++ b/src/modules/admin/consumers/business-activities/business-activities.controller.ts @@ -1,6 +1,9 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common' +import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common' import { BusinessActivitiesService } from './business-activities.service' -import { CreateBusinessActivitiesDto } from './dto/create-business-activities.dto' +import { + CreateBusinessActivitiesDto, + UpdateBusinessActivityDto, +} from './dto/create-business-activities.dto' @Controller('admin/consumers/:consumerId/business_activities') export class BusinessActivitiesController { @@ -24,14 +27,14 @@ export class BusinessActivitiesController { return this.businessActivitiesService.create(consumerId, data) } - // @Put(':id') - // async update( - // @Param('consumerId') consumerId: string, - // @Param('id') id: string, - // @Body() data: UpdateBusinessActivityDto, - // ) { - // return this.businessActivitiesService.update(consumerId, id, data) - // } + @Put(':id') + async update( + @Param('consumerId') consumerId: string, + @Param('id') id: string, + @Body() data: UpdateBusinessActivityDto, + ) { + return this.businessActivitiesService.update(consumerId, id, data) + } // @Delete(':id') // async delete(@Param('id') id: string) { diff --git a/src/modules/admin/consumers/business-activities/business-activities.service.ts b/src/modules/admin/consumers/business-activities/business-activities.service.ts index 01243fd..895a4d5 100644 --- a/src/modules/admin/consumers/business-activities/business-activities.service.ts +++ b/src/modules/admin/consumers/business-activities/business-activities.service.ts @@ -59,9 +59,9 @@ export class BusinessActivitiesService { return ResponseMapper.create(businessActivity) } - async update(id: string, data: any) { + async update(user_id: string, id: string, data: any) { const businessActivity = await this.prisma.businessActivity.update({ - where: { id }, + where: { id, user_id }, data, select: this.defaultSelect, }) diff --git a/src/modules/admin/consumers/business-activities/dto/create-business-activities.dto.ts b/src/modules/admin/consumers/business-activities/dto/create-business-activities.dto.ts index bdfab49..0bfe013 100644 --- a/src/modules/admin/consumers/business-activities/dto/create-business-activities.dto.ts +++ b/src/modules/admin/consumers/business-activities/dto/create-business-activities.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger' +import { ApiProperty, PartialType } from '@nestjs/swagger' import { IsString } from 'class-validator' export class CreateBusinessActivitiesDto { @@ -10,3 +10,5 @@ export class CreateBusinessActivitiesDto { @ApiProperty({}) guild_id: string } + +export class UpdateBusinessActivityDto extends PartialType(CreateBusinessActivitiesDto) {} diff --git a/src/modules/admin/consumers/business-activities/dto/update-business-activities.dto.ts b/src/modules/admin/consumers/business-activities/dto/update-business-activities.dto.ts deleted file mode 100644 index e2c22b3..0000000 --- a/src/modules/admin/consumers/business-activities/dto/update-business-activities.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from '@nestjs/swagger' -import { CreateBusinessActivitiesDto } from './create-business-activities.dto' - -export class UpdateBusinessActivityDto extends PartialType(CreateBusinessActivitiesDto) {} diff --git a/src/modules/admin/users/accounts/accounts.service.ts b/src/modules/admin/users/accounts/accounts.service.ts index 60ae476..934c39b 100644 --- a/src/modules/admin/users/accounts/accounts.service.ts +++ b/src/modules/admin/users/accounts/accounts.service.ts @@ -8,8 +8,6 @@ export class AccountsService { constructor(private readonly prisma: PrismaService) {} async findAll(userId: string) { - console.log('first') - // const accounts = await this.prisma.account.findMany({ // where: { user_id: userId }, // omit: { password: true, user_id: true }, diff --git a/src/modules/auth/auth.utils.ts b/src/modules/auth/auth.utils.ts index b53c6b0..85982d0 100644 --- a/src/modules/auth/auth.utils.ts +++ b/src/modules/auth/auth.utils.ts @@ -8,6 +8,12 @@ import { randomBytes } from 'crypto' import { env } from 'prisma/config' import { AccessTokenPayload } from './models' +export interface ITokenPayload { + type: AccountType + username: string + account_id: string + user?: Record +} @Injectable() export class AuthUtils { constructor( @@ -24,21 +30,20 @@ export class AuthUtils { if (!isMatch) { throw new NotFoundException('اطلاعات ورودی شما اشتباه است') - return false } return true } async getAdminAccountInfo(accountId: string) { - console.log(accountId) - const adminAccount = await this.prisma.account.findUnique({ where: { id: accountId, }, select: { + id: true, admin_account: { select: { + id: true, user: { select: { id: true, @@ -141,7 +146,7 @@ export class AuthUtils { username: account.username, account_id: account.id, user: {}, - } as any + } as AccessTokenPayload switch (account.type) { case AccountType.ADMIN: @@ -158,36 +163,38 @@ export class AuthUtils { break } - const signTokenData = { - type: account.type, - username: account.username, - account_id: account.id, - } as AccessTokenPayload + if (!preparedAccount.user) { + throw new NotFoundException('متاسفانه کاربر شما یافت نشد') + } else { + const signTokenData = { + ...preparedAccount, + } as AccessTokenPayload - const accessToken = this.jwt.sign(signTokenData, { expiresIn: this.ACCESS_TOKEN_EXP }) - const refreshTokenPlain = randomBytes(48).toString('hex') - // const tokenHash = createHash('sha256').update(refreshTokenPlain).digest('hex') - // const expires_at = new Date( - // Date.now() + this.REFRESH_TOKEN_EXP_DAYS * 24 * 60 * 60 * 1000, - // ) + const accessToken = this.jwt.sign(signTokenData, { + expiresIn: this.ACCESS_TOKEN_EXP, + }) + const refreshTokenPlain = randomBytes(48).toString('hex') + // const tokenHash = createHash('sha256').update(refreshTokenPlain).digest('hex') + // const expires_at = new Date( + // Date.now() + this.REFRESH_TOKEN_EXP_DAYS * 24 * 60 * 60 * 1000, + // ) - // await this.prisma.token.upsert({ - // where: { type_account_id: { type: TokenType.ACCESS, account_id: account.id } }, - // update: { token: tokenHash, expires_at }, - // create: { - // token: tokenHash, - // account_id: account.id, - // expires_at, - // type: TokenType.ACCESS, - // }, - // }) + // await this.prisma.token.upsert({ + // where: { type_account_id: { type: TokenType.ACCESS, account_id: account.id } }, + // update: { token: tokenHash, expires_at }, + // create: { + // token: tokenHash, + // account_id: account.id, + // expires_at, + // type: TokenType.ACCESS, + // }, + // }) - console.log(preparedAccount) - - return { - accessToken, - refreshToken: refreshTokenPlain, - account: preparedAccount, + return { + accessToken, + refreshToken: refreshTokenPlain, + account: preparedAccount, + } } } } diff --git a/src/modules/auth/models/index.ts b/src/modules/auth/models/index.ts index d9a4969..c633ba1 100644 --- a/src/modules/auth/models/index.ts +++ b/src/modules/auth/models/index.ts @@ -9,6 +9,12 @@ export interface AccessTokenPayload { account_id: string type: AccountType username: string + user?: AccessTokenUser // license_id?: string // license_expired_at?: string } + +export interface AccessTokenUser extends Record { + id?: string + name?: string +} diff --git a/src/modules/consumer/accounts/accounts.controller.ts b/src/modules/consumer/accounts/accounts.controller.ts new file mode 100644 index 0000000..f8f0d88 --- /dev/null +++ b/src/modules/consumer/accounts/accounts.controller.ts @@ -0,0 +1,38 @@ +import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' +import { AccountsService } from './accounts.service' +import { CreateConsumerAccountDto, UpdateAccountDto } from './dto/account.dto' + +@ApiTags('ConsumerAccounts') +@Controller('consumer/accounts') +export class AccountsController { + constructor(private readonly accountsService: AccountsService) {} + + @Get() + async findAll(@Param('consumerId') consumerId: string) { + return this.accountsService.findAll(consumerId) + } + + @Get(':id') + async findOne(@Param('id') id: string) { + return this.accountsService.findOne(id) + } + + @Post() + async create( + @Param('consumerId') consumerId: string, + @Body() data: CreateConsumerAccountDto, + ) { + return this.accountsService.create(consumerId, data) + } + + @Patch(':id') + async update(@Param('id') id: string, @Body() data: UpdateAccountDto) { + return this.accountsService.update(id, data) + } + + // @Delete(':id') + // async delete(@Param('id') id: string) { + // return this.accountsService.delete(id) + // } +} diff --git a/src/modules/consumer/accounts/accounts.module.ts b/src/modules/consumer/accounts/accounts.module.ts new file mode 100644 index 0000000..77f95a2 --- /dev/null +++ b/src/modules/consumer/accounts/accounts.module.ts @@ -0,0 +1,12 @@ +import { PrismaModule } from '@/prisma/prisma.module' +import { Module } from '@nestjs/common' +import { AccountsController } from './accounts.controller' +import { AccountsService } from './accounts.service' + +@Module({ + imports: [PrismaModule], + controllers: [AccountsController], + providers: [AccountsService], + exports: [AccountsService], +}) +export class ConsumerAccountsModule {} diff --git a/src/modules/consumer/accounts/accounts.service.ts b/src/modules/consumer/accounts/accounts.service.ts new file mode 100644 index 0000000..d4cc769 --- /dev/null +++ b/src/modules/consumer/accounts/accounts.service.ts @@ -0,0 +1,79 @@ +import { PasswordUtil } from '@/common/utils/password.util' +import { AccountStatus, AccountType } from '@/generated/prisma/enums' +import { PrismaService } from '@/prisma/prisma.service' +import { Injectable } from '@nestjs/common' +import { ResponseMapper } from 'common/response/response-mapper' +import { CreateConsumerAccountDto, UpdateAccountDto } from './dto/account.dto' + +@Injectable() +export class AccountsService { + constructor(private readonly prisma: PrismaService) {} + + async findAll(consumerId: string) { + const accounts = await this.prisma.consumerAccount.findMany({ + where: { user_id: consumerId }, + select: { + id: true, + role: true, + created_at: true, + account: { + select: { + username: true, + status: true, + }, + }, + }, + }) + return ResponseMapper.list(accounts) + } + + async findOne(id: string) { + const account = await this.prisma.consumerAccount.findMany({ + where: { id }, + select: { + id: true, + role: true, + created_at: true, + account: { + select: { + username: true, + status: true, + }, + }, + }, + }) + return ResponseMapper.single(account) + } + + async create(consumerId: string, data: CreateConsumerAccountDto) { + const account = await this.prisma.consumerAccount.create({ + data: { + role: data.role, + account: { + create: { + password: await PasswordUtil.hash(data.password), + status: AccountStatus.ACTIVE, + type: AccountType.CONSUMER, + username: data.username, + }, + }, + user: { + connect: { + id: consumerId, + }, + }, + }, + }) + return ResponseMapper.create(account) + } + + async update(id: string, data: UpdateAccountDto) { + const account = await this.prisma.account.update({ where: { id }, data }) + return ResponseMapper.update(account) + } + + async delete(id: string) { + await this.prisma.account.delete({ where: { id } }) + return ResponseMapper.delete() + } +} diff --git a/src/modules/consumer/accounts/dto/account.dto.ts b/src/modules/consumer/accounts/dto/account.dto.ts new file mode 100644 index 0000000..4cd146e --- /dev/null +++ b/src/modules/consumer/accounts/dto/account.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { IsEnum, IsOptional, IsString } from 'class-validator' +import { AccountStatus, ConsumerRole } from 'generated/prisma/enums' + +export class CreateConsumerAccountDto { + @IsString() + @ApiProperty({ required: true }) + username: string + + @IsString() + @ApiProperty({ required: true }) + password: string + + @IsEnum(ConsumerRole) + @ApiProperty({ required: true, enum: ConsumerRole }) + role: ConsumerRole +} + +export class UpdateAccountDto extends PartialType(CreateConsumerAccountDto) { + @IsOptional() + @IsEnum(AccountStatus) + @ApiProperty({ enum: AccountStatus }) + status?: AccountStatus +} diff --git a/src/modules/consumer/accounts/permissions/dto/permission.dto.ts b/src/modules/consumer/accounts/permissions/dto/permission.dto.ts new file mode 100644 index 0000000..28ffdb3 --- /dev/null +++ b/src/modules/consumer/accounts/permissions/dto/permission.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { IsEnum, IsOptional, IsString } from 'class-validator' +import { AccountStatus, AccountType } from 'generated/prisma/enums' + +export class CreateAccountDto { + @IsString() + @ApiProperty() + username: string + + @IsString() + @ApiProperty() + password: string + + @IsEnum(AccountType) + @ApiProperty({ enum: AccountType }) + type: AccountType +} + +export class UpdateAccountDto extends PartialType(CreateAccountDto) { + @IsOptional() + @IsEnum(AccountStatus) + @ApiProperty({ enum: AccountStatus }) + status?: AccountStatus +} diff --git a/src/modules/consumer/accounts/permissions/permissions.controller.ts b/src/modules/consumer/accounts/permissions/permissions.controller.ts new file mode 100644 index 0000000..60332e9 --- /dev/null +++ b/src/modules/consumer/accounts/permissions/permissions.controller.ts @@ -0,0 +1,20 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' +import { CreateAccountDto } from './dto/permission.dto' +import { AccountsService } from './permissions.service' + +@ApiTags('AdminAccounts') +@Controller('admin/users/:userId/accounts/:accountId/permissions') +export class AccountPermissionsController { + constructor(private readonly service: AccountsService) {} + + @Get() + async findAll(@Param('userId') userId: string, @Param('accountId') accountId: string) { + return this.service.findAll(userId, accountId) + } + + @Post() + async create(@Param('userId') userId: string, @Body() data: CreateAccountDto) { + return this.service.create(userId, data) + } +} diff --git a/src/modules/consumer/accounts/permissions/permissions.module.ts b/src/modules/consumer/accounts/permissions/permissions.module.ts new file mode 100644 index 0000000..6e13bd0 --- /dev/null +++ b/src/modules/consumer/accounts/permissions/permissions.module.ts @@ -0,0 +1,12 @@ +import { PrismaModule } from '@/prisma/prisma.module' +import { Module } from '@nestjs/common' +import { AccountPermissionsController } from './permissions.controller' +import { AccountsService } from './permissions.service' + +@Module({ + imports: [PrismaModule], + controllers: [AccountPermissionsController], + providers: [AccountsService], + exports: [AccountsService], +}) +export class AdminAccountPermissionsModule {} diff --git a/src/modules/consumer/accounts/permissions/permissions.service.ts b/src/modules/consumer/accounts/permissions/permissions.service.ts new file mode 100644 index 0000000..abe3412 --- /dev/null +++ b/src/modules/consumer/accounts/permissions/permissions.service.ts @@ -0,0 +1,116 @@ +import { PrismaService } from '@/prisma/prisma.service' +import { Injectable } from '@nestjs/common' +import { ResponseMapper } from 'common/response/response-mapper' +import { CreateAccountDto } from './dto/permission.dto' + +@Injectable() +export class AccountsService { + constructor(private readonly prisma: PrismaService) {} + + async findAll(userId: string, account_id: string) { + const businessActivities = await this.prisma.businessActivity.findMany({ + where: { + user_id: userId, + }, + select: { + id: true, + name: true, + complexes: { + select: { + id: true, + name: true, + pos_list: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }) + + const permissions = await this.prisma.permissionConsumer.findMany({ + where: { + account_id, + }, + select: { + posPermissions: { + select: { + pos_id: true, + role: true, + }, + }, + businessPermissions: { + select: { + business_id: true, + role: true, + }, + }, + complexPermissions: { + select: { + complex_id: true, + role: true, + }, + }, + }, + }) + + const businesses = [] as any + const complexes = [] as any + const poses = [] as any + + businessActivities.forEach(businessActivity => { + businesses.push({ + id: businessActivity.id, + name: businessActivity.name, + hasAccess: false, + role: null, + }) + + businessActivity.complexes.forEach(complex => { + complexes.push({ + id: complex.id, + name: complex.name, + hasAccess: false, + role: null, + }) + + complex.pos_list.forEach(pos => { + poses.push({ + id: pos.id, + name: pos.name, + hasAccess: false, + role: null, + }) + }) + }) + }) + + permissions.forEach(permission => { + permission.posPermissions.forEach(posPermission => { + poses.map(pos => { + if (pos.id === posPermission.pos_id) { + return { + ...pos, + role: posPermission.role, + hasAccess: true, + } + } + return pos + }) + }) + }) + } + + async findOne(id: string) { + const account = await this.prisma.account.findUnique({ + where: { id }, + }) + return ResponseMapper.single(account) + } + + async create(userId: string, data: CreateAccountDto) { + return ResponseMapper.create({}) + } +} diff --git a/src/modules/consumer/business-activities/business-activities.controller.ts b/src/modules/consumer/business-activities/business-activities.controller.ts new file mode 100644 index 0000000..dfa9ab2 --- /dev/null +++ b/src/modules/consumer/business-activities/business-activities.controller.ts @@ -0,0 +1,28 @@ +import { TokenAccount } from '@/common/decorators/tokenInfo.decorator' +import { Body, Controller, Get, Param, Patch } from '@nestjs/common' +import { BusinessActivitiesService } from './business-activities.service' +import { UpdateBusinessActivityDto } from './dto/create-business-activities.dto' + +@Controller('consumer/business_activities') +export class BusinessActivitiesController { + constructor(private readonly service: BusinessActivitiesService) {} + + @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) + } +} diff --git a/src/modules/consumer/business-activities/business-activities.module.ts b/src/modules/consumer/business-activities/business-activities.module.ts new file mode 100644 index 0000000..27a8989 --- /dev/null +++ b/src/modules/consumer/business-activities/business-activities.module.ts @@ -0,0 +1,12 @@ +import { PrismaModule } from '@/prisma/prisma.module' +import { Module } from '@nestjs/common' +import { BusinessActivitiesController } from './business-activities.controller' +import { BusinessActivitiesService } from './business-activities.service' +import { ConsumerBusinessActivityComplexesModule } from './complexes/complexes.module' + +@Module({ + imports: [PrismaModule, ConsumerBusinessActivityComplexesModule], + controllers: [BusinessActivitiesController], + providers: [BusinessActivitiesService], +}) +export class ConsumerBusinessActivitiesModule {} diff --git a/src/modules/consumer/business-activities/business-activities.service.ts b/src/modules/consumer/business-activities/business-activities.service.ts new file mode 100644 index 0000000..b687943 --- /dev/null +++ b/src/modules/consumer/business-activities/business-activities.service.ts @@ -0,0 +1,49 @@ +import { BusinessActivitySelect } from '@/generated/prisma/models' +import { PrismaService } from '@/prisma/prisma.service' +import { Injectable } from '@nestjs/common' +import { ResponseMapper } from 'common/response/response-mapper' + +@Injectable() +export class BusinessActivitiesService { + constructor(private readonly prisma: PrismaService) {} + + defaultSelect = { + guild: { + select: { + id: true, + code: true, + name: true, + }, + }, + id: true, + name: true, + created_at: true, + } as BusinessActivitySelect + + async findAll(user_id: string) { + const businessActivities = await this.prisma.businessActivity.findMany({ + where: { + user_id, + }, + select: this.defaultSelect, + }) + return ResponseMapper.list(businessActivities) + } + + async findOne(user_id: string, id: string) { + const businessActivity = await this.prisma.businessActivity.findUniqueOrThrow({ + where: { user_id, id }, + select: this.defaultSelect, + }) + return ResponseMapper.single(businessActivity) + } + + async update(user_id: string, id: string, data: any) { + const businessActivity = await this.prisma.businessActivity.update({ + where: { id, user_id }, + data, + select: this.defaultSelect, + }) + return ResponseMapper.update(businessActivity) + } +} diff --git a/src/modules/consumer/business-activities/complexes/complexes.controller.ts b/src/modules/consumer/business-activities/complexes/complexes.controller.ts new file mode 100644 index 0000000..c8f9c9a --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/complexes.controller.ts @@ -0,0 +1,38 @@ +import { TokenAccount } from '@/common/decorators/tokenInfo.decorator' +import { Body, Controller, Get, Param, Patch } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' +import { BusinessActivityComplexesService } from './complexes.service' +import { UpdateComplexDto } from './dto/complex.dto' + +@ApiTags('AdminBusinessActivityComplexes') +@Controller('consumer/business_activities/:businessActivityId/complexes') +export class BusinessActivityComplexesController { + constructor(private readonly service: BusinessActivityComplexesService) {} + + @Get() + async findAll( + @TokenAccount('userId') userId: string, + @Param('businessActivityId') businessActivityId: string, + ) { + return this.service.findAll(userId, businessActivityId) + } + + @Get(':id') + async findOne( + @TokenAccount('userId') userId: string, + @Param('businessActivityId') businessActivityId: string, + @Param('id') id: string, + ) { + return this.service.findOne(userId, businessActivityId, id) + } + + @Patch(':id') + async update( + @TokenAccount('userId') userId: string, + @Param('businessActivityId') businessActivityId: string, + @Param('id') id: string, + @Body() data: UpdateComplexDto, + ) { + return this.service.update(userId, businessActivityId, id, data) + } +} diff --git a/src/modules/consumer/business-activities/complexes/complexes.module.ts b/src/modules/consumer/business-activities/complexes/complexes.module.ts new file mode 100644 index 0000000..5e1b130 --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/complexes.module.ts @@ -0,0 +1,12 @@ +import { PrismaModule } from '@/prisma/prisma.module' +import { Module } from '@nestjs/common' +import { BusinessActivityComplexesController } from './complexes.controller' +import { BusinessActivityComplexesService } from './complexes.service' +import { ConsumerComplexPosesModule } from './poses/poses.module' + +@Module({ + imports: [PrismaModule, ConsumerComplexPosesModule], + controllers: [BusinessActivityComplexesController], + providers: [BusinessActivityComplexesService], +}) +export class ConsumerBusinessActivityComplexesModule {} diff --git a/src/modules/consumer/business-activities/complexes/complexes.service.ts b/src/modules/consumer/business-activities/complexes/complexes.service.ts new file mode 100644 index 0000000..3826ffb --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/complexes.service.ts @@ -0,0 +1,75 @@ +import { ComplexSelect } from '@/generated/prisma/models' +import { PrismaService } from '@/prisma/prisma.service' +import { Injectable } from '@nestjs/common' +import { ResponseMapper } from 'common/response/response-mapper' +import { UpdateComplexDto } from './dto/complex.dto' + +@Injectable() +export class BusinessActivityComplexesService { + constructor(private readonly prisma: PrismaService) {} + + defaultSelect = { + id: true, + name: true, + address: true, + tax_id: true, + created_at: true, + } as ComplexSelect + + async findAll(user_id: string, business_activity_id: string) { + const accounts = await this.prisma.complex.findMany({ + where: { + business_activity: { + id: business_activity_id, + user: { + id: user_id, + }, + }, + }, + select: this.defaultSelect, + }) + return ResponseMapper.list(accounts) + } + + async findOne(user_id: string, business_activity_id: string, id: string) { + const account = await this.prisma.complex.findUnique({ + where: { + business_activity: { + id: business_activity_id, + user: { + id: user_id, + }, + }, + id, + }, + select: this.defaultSelect, + }) + return ResponseMapper.single(account) + } + + async update( + user_id: string, + business_activity_id: string, + id: string, + data: UpdateComplexDto, + ) { + const account = await this.prisma.complex.update({ + where: { + business_activity: { + id: business_activity_id, + user: { + id: user_id, + }, + }, + id, + }, + data, + }) + return ResponseMapper.update(account) + } + + // async delete(id: string) { + // await this.prisma.complex.delete({ where: { id } }) + // return ResponseMapper.delete() + // } +} diff --git a/src/modules/consumer/business-activities/complexes/dto/complex.dto.ts b/src/modules/consumer/business-activities/complexes/dto/complex.dto.ts new file mode 100644 index 0000000..f61ba75 --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/dto/complex.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { IsString } from 'class-validator' + +export class CreateComplexDto { + @IsString() + @ApiProperty({}) + name: string + + @IsString() + @ApiProperty({}) + tax_id: string + + @IsString() + @ApiProperty({}) + address: string +} + +export class UpdateComplexDto extends PartialType(CreateComplexDto) {} diff --git a/src/modules/consumer/business-activities/complexes/poses/dto/pos.dto.ts b/src/modules/consumer/business-activities/complexes/poses/dto/pos.dto.ts new file mode 100644 index 0000000..6d5f88c --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/poses/dto/pos.dto.ts @@ -0,0 +1,39 @@ +import { POSStatus, POSType } from '@/generated/prisma/enums' +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { IsEnum, IsOptional, IsString } from 'class-validator' + +export class CreatePosDto { + @IsString() + @ApiProperty({}) + name: string + + @IsString() + @ApiProperty({}) + serial: string + + // @IsString() + // @IsOptional() + // @ApiProperty({ required: false }) + // model: string + + @IsEnum(POSType) + @ApiProperty({ enum: POSType }) + pos_type: POSType + + @IsString() + @IsOptional() + @ApiProperty({}) + device_id: string + + @IsString() + @IsOptional() + @ApiProperty() + provider_id: string +} + +export class UpdatePosDto extends PartialType(CreatePosDto) { + @IsEnum(POSStatus) + @IsOptional() + @ApiProperty({ enum: POSStatus }) + status: POSStatus +} diff --git a/src/modules/consumer/business-activities/complexes/poses/poses.controller.ts b/src/modules/consumer/business-activities/complexes/poses/poses.controller.ts new file mode 100644 index 0000000..1e8486d --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/poses/poses.controller.ts @@ -0,0 +1,49 @@ +import { TokenAccount } from '@/common/decorators/tokenInfo.decorator' +import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' +import { CreatePosDto, UpdatePosDto } from './dto/pos.dto' +import { ComplexPosesService } from './poses.service' + +@ApiTags('ConsumerComplexPoses') +@Controller('consumer/business_activities/:businessActivityId/complexes/:complexId/poses') +export class ComplexPosesController { + constructor(private readonly service: ComplexPosesService) {} + + @Get() + async findAll( + @TokenAccount('userId') userId: string, + @Param('businessActivityId') businessActivityId: string, + @Param('complexId') complexId: string, + ) { + return this.service.findAll(userId, businessActivityId, complexId) + } + + @Get(':id') + async findOne( + @Param('businessActivityId') businessActivityId: string, + @Param('complexId') complexId: string, + @Param('id') id: string, + ) { + return this.service.findOne(businessActivityId, complexId, id) + } + + @Post() + async create(@Param('complexId') complexId: string, @Body() data: CreatePosDto) { + return this.service.create(complexId, data) + } + + @Patch(':id') + async update( + @Param('businessActivityId') businessActivityId: string, + @Param('complexId') complexId: string, + @Param('id') id: string, + @Body() data: UpdatePosDto, + ) { + return this.service.update(businessActivityId, complexId, id, data) + } + + // @Delete(':id') + // async delete(@Param('id') id: string) { + // return this.businessActivityAccountsService.delete(id) + // } +} diff --git a/src/modules/consumer/business-activities/complexes/poses/poses.module.ts b/src/modules/consumer/business-activities/complexes/poses/poses.module.ts new file mode 100644 index 0000000..8718b22 --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/poses/poses.module.ts @@ -0,0 +1,12 @@ +import { PrismaModule } from '@/prisma/prisma.module' +import { Module } from '@nestjs/common' +import { ComplexPosesController } from './poses.controller' +import { ComplexPosesService } from './poses.service' + +@Module({ + imports: [PrismaModule], + controllers: [ComplexPosesController], + providers: [ComplexPosesService], + exports: [ComplexPosesService], +}) +export class ConsumerComplexPosesModule {} diff --git a/src/modules/consumer/business-activities/complexes/poses/poses.service.ts b/src/modules/consumer/business-activities/complexes/poses/poses.service.ts new file mode 100644 index 0000000..ff699b7 --- /dev/null +++ b/src/modules/consumer/business-activities/complexes/poses/poses.service.ts @@ -0,0 +1,171 @@ +import { POSStatus } from '@/generated/prisma/enums' +import { PosCreateInput, PosSelect } from '@/generated/prisma/models' +import { PrismaService } from '@/prisma/prisma.service' +import { Injectable } from '@nestjs/common' +import { ResponseMapper } from 'common/response/response-mapper' +import { CreatePosDto, UpdatePosDto } from './dto/pos.dto' + +@Injectable() +export class ComplexPosesService { + constructor(private readonly prisma: PrismaService) {} + + defaultSelect = { + 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, + }, + }, + }, + }, + } as PosSelect + + defaultInsert = (data: CreatePosDto, complex_id: string) => { + const { device_id, provider_id, ...rest } = data + + return { + ...rest, + complex: { + connect: { + id: complex_id, + }, + }, + provider: provider_id + ? { + connect: { + id: provider_id, + }, + } + : undefined, + device: device_id + ? { + connect: { + id: device_id, + }, + } + : undefined, + } as PosCreateInput + } + + async findAll(user_id: string, business_activity_id: string, complex_id: string) { + const poses = await this.prisma.pos.findMany({ + where: { + complex: { + id: complex_id, + business_activity: { + id: business_activity_id, + user_id, + }, + }, + }, + select: this.defaultSelect, + }) + console.log(poses) + + return ResponseMapper.list(poses) + } + + async findOne(business_activity_id: string, complex_id: string, id: string) { + const account = await this.prisma.pos.findUnique({ + where: { + complex: { + id: complex_id, + business_activity: { + id: business_activity_id, + }, + }, + id, + }, + select: this.defaultSelect, + }) + return ResponseMapper.single(account) + } + + async create(complex_id: string, data: CreatePosDto) { + const account = await this.prisma.pos.create({ + data: { + ...this.defaultInsert(data, complex_id), + status: POSStatus.ACTIVE, + }, + }) + return ResponseMapper.create(account) + } + + async update( + business_activity_id: string, + complex_id: string, + id: string, + data: UpdatePosDto, + ) { + const { device_id, provider_id, ...rest } = data + const account = await this.prisma.pos.update({ + where: { + complex: { + id: complex_id, + business_activity: { + id: business_activity_id, + }, + }, + id, + }, + data: { + complex: { + connect: { + id: complex_id, + }, + }, + provider: provider_id + ? { + connect: { + id: provider_id, + }, + } + : undefined, + device: device_id + ? { + connect: { + id: device_id, + }, + } + : undefined, + ...rest, + }, + }) + return ResponseMapper.update(account) + } + + // async delete(id: string) { + // await this.prisma.complex.delete({ where: { id } }) + // return ResponseMapper.delete() + // } +} diff --git a/src/modules/consumer/business-activities/dto/create-business-activities.dto.ts b/src/modules/consumer/business-activities/dto/create-business-activities.dto.ts new file mode 100644 index 0000000..952d921 --- /dev/null +++ b/src/modules/consumer/business-activities/dto/create-business-activities.dto.ts @@ -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) {} diff --git a/src/modules/consumer/consumer.controller.ts b/src/modules/consumer/consumer.controller.ts new file mode 100644 index 0000000..85e951d --- /dev/null +++ b/src/modules/consumer/consumer.controller.ts @@ -0,0 +1,6 @@ +import { Controller } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' + +@ApiTags('Consumer') +@Controller('consumer') +export class ConsumerController {} diff --git a/src/modules/consumer/consumer.guard.ts b/src/modules/consumer/consumer.guard.ts new file mode 100644 index 0000000..3f395f0 --- /dev/null +++ b/src/modules/consumer/consumer.guard.ts @@ -0,0 +1,14 @@ +// import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common' +// import { Observable } from 'rxjs' + +// @Injectable() +// export class AdminGuard implements CanActivate { +// canActivate( +// _context: ExecutionContext, +// ): boolean | Promise | Observable { +// // Replace with real auth/permission checks (e.g., check JWT claims) +// const request = _context.switchToHttp().getRequest() +// const isAdminHeader = request.headers['x-admin'] +// return isAdminHeader === 'true' || request.isAdminRequest === true +// } +// } diff --git a/src/modules/consumer/consumer.middleware.ts b/src/modules/consumer/consumer.middleware.ts new file mode 100644 index 0000000..ce736db --- /dev/null +++ b/src/modules/consumer/consumer.middleware.ts @@ -0,0 +1,20 @@ +import { checkAndDecodeJwtToken } from '@/common/utils/jwt-user.util' +import { AccountType } from '@/generated/prisma/enums' +import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import { NextFunction, Request, Response } from 'express' + +@Injectable() +export class ConsumerMiddleware implements NestMiddleware { + constructor(private jwtService: JwtService) {} + use(req: Request, _res: Response, next: NextFunction) { + const decodedToken = checkAndDecodeJwtToken(req, this.jwtService) + if (decodedToken?.type === AccountType.CONSUMER) { + req.decodedToken = decodedToken + + return next() + } + + throw new UnauthorizedException('شما دسترسی لازم را ندارید.') + } +} diff --git a/src/modules/consumer/consumer.module.ts b/src/modules/consumer/consumer.module.ts new file mode 100644 index 0000000..f7efe8c --- /dev/null +++ b/src/modules/consumer/consumer.module.ts @@ -0,0 +1,21 @@ +import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import { ConsumerAccountsModule } from './accounts/accounts.module' +import { ConsumerBusinessActivitiesModule } from './business-activities/business-activities.module' +import { ConsumerController } from './consumer.controller' +import { ConsumerMiddleware } from './consumer.middleware' + +@Module({ + controllers: [ConsumerController], + imports: [ConsumerAccountsModule, ConsumerBusinessActivitiesModule], + providers: [JwtService], +}) +export class ConsumerModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(ConsumerMiddleware).forRoutes({ + path: '/consumer*path', + method: RequestMethod.ALL, + version: '1', + }) + } +}