add : reseller

This commit is contained in:
2026-04-13 11:57:13 +03:30
parent fccea733e3
commit bb42b393c4
13 changed files with 152 additions and 21 deletions
@@ -4,8 +4,9 @@ import { ApiBearerAuth } from "@nestjs/swagger";
import { AdminRouteGuard } from "../../modules/auth/guards/admin.guard";
import { JwtAuthGuard } from "../../modules/auth/guards/auth.guard";
import { PermissionsGuard } from "../../modules/auth/guards/permission.guard";
import { ResellerRouteGuard } from "../../modules/auth/guards/reseller.guard";
// import { RoleGuard } from "../../modules/auth/guards/role.guard";
export function AuthGuards() {
return applyDecorators(UseGuards(JwtAuthGuard, PermissionsGuard, AdminRouteGuard), ApiBearerAuth("authorization"));
return applyDecorators(UseGuards(JwtAuthGuard, PermissionsGuard, AdminRouteGuard,ResellerRouteGuard), ApiBearerAuth("authorization"));
}
+4
View File
@@ -0,0 +1,4 @@
export const RESELLER_ROUTE = "shouldReseller";
import { SetMetadata } from "@nestjs/common";
export const ResellerRoute = (isResellerRoute: boolean = true) => SetMetadata(RESELLER_ROUTE, isResellerRoute);
+4 -4
View File
@@ -34,11 +34,11 @@ export class AuthController {
}
@ApiOperation({ summary: "request to login with otp" })
@ApiOperation({ summary: "request to login with otp as reseller" })
@HttpCode(HttpStatus.OK)
@Post("otp/send/reseller")
sendOtpReseller(@Body() requestOtpDto: RequestOtpDto) {
return this.authService.requestLoginOtpAgentAndReseller(requestOtpDto);
return this.authService.requestLoginOtpReseller(requestOtpDto);
}
@ApiOperation({ summary: "verify otp for login" })
@@ -79,9 +79,9 @@ export class AuthController {
@ApiOperation({ summary: "verify otp for login" })
@HttpCode(HttpStatus.OK)
@Post("otp/verify/agent")
@Post("otp/verify/reseller")
resellerVerifyOtp(@Body() verifyOtpDto: VerifyOtpDto) {
return this.authService.requestLoginOtpAgentAndReseller(verifyOtpDto);
return this.authService.resellerVerifyLoginOtp(verifyOtpDto);
}
@AuthGuards()
+26
View File
@@ -0,0 +1,26 @@
import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from "@nestjs/common";
import { FastifyRequest } from "fastify";
import { AuthMessage } from "../../../common/enums/message.enum";
import { Reflector } from "@nestjs/core";
import { RESELLER_ROUTE } from "../../../common/decorators/reseller.decorator";
@Injectable()
export class ResellerRouteGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredReseller = this.reflector.getAllAndOverride<boolean>(RESELLER_ROUTE, [context.getHandler(), context.getClass()]);
if (!requiredReseller) return true;
const req = context.switchToHttp().getRequest<FastifyRequest>();
const user = req.user;
if (!user) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS);
if (!user.isReseller) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS);
return true;
}
}
@@ -4,6 +4,7 @@ import { PermissionEnum } from "../../users/enums/permission.enum";
export interface ITokenPayload {
id: string;
isAdmin: boolean;
isReseller: boolean;
permissions: PermissionEnum[];
audience?: string; // Client application identifier for SSO
}
+4 -5
View File
@@ -112,11 +112,11 @@ export class AuthService {
//****************** */
//****************** */
async requestLoginOtpAgentAndReseller(requestOtpDto: RequestOtpDto) {
async requestLoginOtpReseller(requestOtpDto: RequestOtpDto) {
const { phone } = requestOtpDto;
// check if agent with this phone exist or not
const { reseller, agent } = await this.checkAgentExistWithPhone(phone)
await this.checkAgentExistWithPhone(phone)
const existCode = await this.otpService.checkExistOtp(phone, "LOGIN_RESELLER_AGENT");
if (existCode) {
@@ -133,8 +133,7 @@ export class AuthService {
return {
message: AuthMessage.OTP_SENT + otpCode,
reseller,
agent
};
}
@@ -244,7 +243,7 @@ export class AuthService {
if (user.deletedAt) throw new BadRequestException(AuthMessage.ACCESS_DENIED);
const tokens = await this.tokensService.generateTokens(user);
const tokens = await this.tokensService.generateTokensForReseller(user);
return {
message: AuthMessage.LOGIN_SUCCESS,
@@ -45,6 +45,7 @@ export class SSOService {
// Generate token with client-specific audience
const payload: ITokenPayload = {
id: user.id,
isReseller:false,
isAdmin: user.roles.some((r) => r.isAdmin),
permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])),
audience: clientId,
@@ -105,6 +106,7 @@ export class SSOService {
const payload: ITokenPayload = {
id: user.id,
isAdmin: user.roles.some((r) => r.isAdmin),
isReseller:false,
permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])),
audience: clientId,
};
+14 -1
View File
@@ -19,7 +19,7 @@ export class TokensService {
private readonly jwtService: JwtService,
private readonly refreshTokensRepository: RefreshTokensRepository,
private readonly dataSource: DataSource,
) {}
) { }
// ----------- generate token -----------------
async generateTokens(user: User, queryRunner?: QueryRunner) {
@@ -27,6 +27,19 @@ export class TokensService {
{
id: user.id,
isAdmin: user.roles.some((r) => r.isAdmin),
isReseller: false,
permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])),
},
queryRunner,
);
}
// -----------------------------
async generateTokensForReseller(user: User, queryRunner?: QueryRunner) {
return this.generateAccessAndRefreshToken(
{
id: user.id,
isAdmin: user.roles.some((r) => r.isAdmin),
isReseller: true,
permissions: user.roles.flatMap((r) => (r?.permissions?.length ? r.permissions.map((p) => p.name) : [])),
},
queryRunner,
+9
View File
@@ -0,0 +1,9 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsString } from "class-validator";
export class CreateResellerAgentDto {
@IsNotEmpty()
@IsString()
@ApiProperty()
userId: string;
}
+35 -4
View File
@@ -1,29 +1,60 @@
import { Controller, Get, Post, Body } from '@nestjs/common';
import { Controller, Get, Post, Body, Delete } from '@nestjs/common';
import { ResellerService } from './reseller.service';
import { ApiOperation } from '@nestjs/swagger';
import { PermissionsDec } from '../../common/decorators/permission.decorator';
import { PermissionEnum } from '../users/enums/permission.enum';
import { CreateResellerDto } from './dto/create-reseller.dto';
import { AuthGuards } from '../../common/decorators/auth-guard.decorator';
import { AdminRoute } from '../../common/decorators/admin.decorator';
import { ResellerRoute } from '../../common/decorators/reseller.decorator';
import { UserDec } from '../../common/decorators/user.decorator';
import { CreateResellerAgentDto } from './dto/create-reseller-agent.dto';
@Controller('reseller')
@AuthGuards()
export class ResellerController {
constructor(
private readonly resellerService: ResellerService,
) { }
@ApiOperation({ summary: "create Reseller ==> admin route" })
@AdminRoute()
@PermissionsDec(PermissionEnum.RESELLER)
@Post("reseller")
@Post()
createReseller(@Body() dto: CreateResellerDto) {
return this.resellerService.create(dto)
}
@ApiOperation({ summary: "get Resellers ==> admin route" })
@AdminRoute()
@PermissionsDec(PermissionEnum.RESELLER)
@Get("reseller")
@Get()
getResellers() {
return this.resellerService.findAll()
}
// =========== Reseller-->Agent =============
@ApiOperation({ summary: "get Reseller agents ==> reseller route" })
@ResellerRoute()
@Get('agents')
getResellerAgents(@UserDec("id") userId: string) {
return this.resellerService.finResellerAgents(userId)
}
@ApiOperation({ summary: "Create Reseller agent ==> reseller route" })
@ResellerRoute()
@Post('agents')
createResellerAgent(@UserDec("id") userId: string, @Body() dto: CreateResellerAgentDto) {
return this.resellerService.createResellerAgent(userId, dto.userId)
}
@ApiOperation({ summary: "Remove Reseller agent ==> reseller route" })
@ResellerRoute()
@Delete('agents')
deleteResellerAgent(@UserDec("id") userId: string, @Body() dto: CreateResellerAgentDto) {
return this.resellerService.removeResellerAgent(userId, dto.userId)
}
}
+44 -1
View File
@@ -1,4 +1,4 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { UsersService } from '../users/providers/users.service';
import { CreateResellerDto } from './dto/create-reseller.dto';
import { ResellerRepository } from './repositories/reseller.repository';
@@ -37,4 +37,47 @@ export class ResellerService {
}
})
}
async finResellerAgents(userId: string) {
const reseller = await this.FindOneOrFailByUserId(userId)
return reseller.agents
}
async FindOneOrFailByUserId(userId: string) {
const reseller = await this.resellerRepository.findOne({
where: { owner: { id: userId } },
relations: { agents: true }
})
if (!reseller) {
throw new NotFoundException()
}
return reseller
}
async createResellerAgent(userId: string, userTobeAgent: string) {
const reseller = await this.FindOneOrFailByUserId(userId)
const { user } = await this.usersService.findOneById(userTobeAgent)
user.reseller = reseller
await this.usersService.save(user)
return user
}
async removeResellerAgent(userId: string, agentToBeDeleted: string) {
await this.FindOneOrFailByUserId(userId)
const { user } = await this.usersService.findOneById(agentToBeDeleted)
user.reseller = undefined
await this.usersService.save(user)
return user
}
}
+4 -2
View File
@@ -48,7 +48,7 @@ export class UsersService {
private readonly smsService: SmsService,
private readonly cacheService: CacheService,
private readonly referralsService: ReferralsService,
) { }
) { }
/************************************************************ */
@@ -416,5 +416,7 @@ export class UsersService {
/************************************************************ */
async save(user: User) {
return this.userRepository.save(user)
}
}