add : reseller
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddREseller1775992337764 implements MigrationInterface {
|
||||
name = 'AddREseller1775992337764'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "reseller" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "name" character varying(100) NOT NULL, "ownerId" uuid, CONSTRAINT "UQ_4993ba05f9437d2c32ea8adde42" UNIQUE ("name"), CONSTRAINT "REL_e2c8deee8c9000aefd91c17486" UNIQUE ("ownerId"), CONSTRAINT "PK_fbb203c7b490e89a011e62f2faf" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "resellerId" uuid`);
|
||||
await queryRunner.query(`ALTER TABLE "reseller" ADD CONSTRAINT "FK_e2c8deee8c9000aefd91c174860" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_f39518997d27b06d20bd1b08af0" FOREIGN KEY ("resellerId") REFERENCES "reseller"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_f39518997d27b06d20bd1b08af0"`);
|
||||
await queryRunner.query(`ALTER TABLE "reseller" DROP CONSTRAINT "FK_e2c8deee8c9000aefd91c174860"`);
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "resellerId"`);
|
||||
await queryRunner.query(`DROP TABLE "reseller"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,6 +62,7 @@ export const enum AuthMessage {
|
||||
CURRENT_PASSWORD_INVALID = "رمز عبور فعلی نامعتبر است",
|
||||
TOKEN_EXPIRED_OR_INVALID = "توکن منقضی شده یا نامعتبر است",
|
||||
ACCESS_DENIED = "دسترسی شما محدود شده است",
|
||||
NOT_AGENT="شما مجوز دسترسی به بخش نمایندگان را ندارید"
|
||||
}
|
||||
|
||||
export const enum UserMessage {
|
||||
|
||||
@@ -33,6 +33,14 @@ export class AuthController {
|
||||
return this.authService.requestLoginOtp(requestOtpDto);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation({ summary: "request to login with otp" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("otp/send/reseller")
|
||||
sendOtpReseller(@Body() requestOtpDto: RequestOtpDto) {
|
||||
return this.authService.requestLoginOtpAgentAndReseller(requestOtpDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "verify otp for login" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("otp/verify")
|
||||
@@ -69,6 +77,13 @@ export class AuthController {
|
||||
return this.authService.adminVerifyLoginOtp(verifyOtpDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "verify otp for login" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("otp/verify/agent")
|
||||
resellerVerifyOtp(@Body() verifyOtpDto: VerifyOtpDto) {
|
||||
return this.authService.requestLoginOtpAgentAndReseller(verifyOtpDto);
|
||||
}
|
||||
|
||||
@AuthGuards()
|
||||
@ApiOperation({ summary: "Update user password" })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
@@ -20,6 +20,7 @@ import { VerifyOtpDto } from "../DTO/verify-otp.dto";
|
||||
import { ForgetPasswordDto } from "../DTO/forget-password.dto";
|
||||
import { VerifyForgotPasswordOtpDto } from "../DTO/verify-forgot-otp.dto";
|
||||
import { EmailService } from "../../utils/providers/email.service";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@@ -111,6 +112,35 @@ export class AuthService {
|
||||
//****************** */
|
||||
//****************** */
|
||||
|
||||
async requestLoginOtpAgentAndReseller(requestOtpDto: RequestOtpDto) {
|
||||
const { phone } = requestOtpDto;
|
||||
|
||||
// check if agent with this phone exist or not
|
||||
const { reseller, agent } = await this.checkAgentExistWithPhone(phone)
|
||||
|
||||
const existCode = await this.otpService.checkExistOtp(phone, "LOGIN_RESELLER_AGENT");
|
||||
if (existCode) {
|
||||
return {
|
||||
message: AuthMessage.OTP_ALREADY_SENT,
|
||||
ttlSecond: existCode,
|
||||
};
|
||||
}
|
||||
|
||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN_RESELLER_AGENT");
|
||||
//
|
||||
this.logger.log(`otp code: ${otpCode} for phone: ${phone}`);
|
||||
await this.notificationQueue.addLoginOtpNotification({ phone, code: otpCode });
|
||||
|
||||
return {
|
||||
message: AuthMessage.OTP_SENT + otpCode,
|
||||
reseller,
|
||||
agent
|
||||
};
|
||||
}
|
||||
|
||||
//****************** */
|
||||
//****************** */
|
||||
|
||||
async verifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
||||
const { code, phone, referralCode } = verifyOtpDto;
|
||||
|
||||
@@ -198,6 +228,31 @@ export class AuthService {
|
||||
}
|
||||
//****************** */
|
||||
|
||||
async resellerVerifyLoginOtp(verifyOtpDto: VerifyOtpDto) {
|
||||
const { code, phone } = verifyOtpDto;
|
||||
|
||||
await this.checkResellerOtpWithPhone(phone, code)
|
||||
|
||||
const { agent, reseller } = await this.checkAgentExistWithPhone(phone);
|
||||
|
||||
|
||||
const user: User | null = agent ?? reseller
|
||||
|
||||
if (!user) {
|
||||
throw new BadRequestException(AuthMessage.USER_NOT_FOUND)
|
||||
}
|
||||
|
||||
if (user.deletedAt) throw new BadRequestException(AuthMessage.ACCESS_DENIED);
|
||||
|
||||
const tokens = await this.tokensService.generateTokens(user);
|
||||
|
||||
return {
|
||||
message: AuthMessage.LOGIN_SUCCESS,
|
||||
...tokens,
|
||||
};
|
||||
}
|
||||
//****************** */
|
||||
|
||||
async changePassword(userId: string, updateDto: ChangePasswordDto) {
|
||||
const { user } = await this.usersService.findOneById(userId);
|
||||
|
||||
@@ -249,6 +304,27 @@ export class AuthService {
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private async checkResellerOtpWithPhone(phone: string, otpCode: string) {
|
||||
const isValid = await this.otpService.verifyOtp(phone, otpCode, "LOGIN_RESELLER_AGENT");
|
||||
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
|
||||
|
||||
return true;
|
||||
}
|
||||
//****************** */
|
||||
//****************** */
|
||||
private async checkAgentExistWithPhone(phone: string) {
|
||||
|
||||
const { reseller } = await this.usersService.getResellerByUserPhone(phone);
|
||||
|
||||
const { agent } = await this.usersService.getAgentByUserPhone(phone);
|
||||
|
||||
if (!reseller && !agent) {
|
||||
throw new BadRequestException(AuthMessage.USER_NOT_FOUND)
|
||||
}
|
||||
|
||||
return { reseller, agent }
|
||||
}
|
||||
//****************** */
|
||||
//****************** */
|
||||
async requestForgotPasswordOtp(dto: ForgetPasswordDto) {
|
||||
@@ -270,7 +346,7 @@ export class AuthService {
|
||||
}
|
||||
|
||||
const otpCode = await this.otpService.generateAndSetInCache(email, "FORGOT_PASSWORD");
|
||||
|
||||
|
||||
await this.emailService.sendOtpEmail({ userEmail: email, otp: otpCode });
|
||||
|
||||
this.logger.log(`otp code: ${otpCode} for email: ${email}`);
|
||||
@@ -306,4 +382,9 @@ export class AuthService {
|
||||
private checkUserIsAdmin(roles: Role[]) {
|
||||
return roles.some((role) => role.isAdmin);
|
||||
}
|
||||
//****************** */
|
||||
//****************** */
|
||||
// private checkUserIsAgent(roles: Role[]) {
|
||||
// return roles.some((role) => role.isAgent);
|
||||
// }
|
||||
}
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsMobilePhone, IsNotEmpty, IsString, Length } from "class-validator";
|
||||
import { AuthMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
|
||||
export class CreateResellerDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@Length(2, 50,)
|
||||
@ApiProperty({ description: "name", example: "mahyar" })
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
||||
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
||||
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
|
||||
@ApiProperty({ description: "phone number", default: "09922320740" })
|
||||
phone: string;
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { Column, Entity, JoinColumn, OneToMany, OneToOne } from "typeorm";
|
||||
|
||||
import { User } from "./user.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
|
||||
@Entity()
|
||||
export class Reseller extends BaseEntity {
|
||||
@Column({ type: "varchar", length: 100, unique: true })
|
||||
name: string;
|
||||
|
||||
//---------------------------------------
|
||||
|
||||
@OneToOne(() => User,user=>user.ownedReseller)
|
||||
@JoinColumn()
|
||||
owner!: User;
|
||||
|
||||
@OneToMany(() => User, user => user.reseller)
|
||||
agents: User[];
|
||||
}
|
||||
@@ -15,6 +15,9 @@ export class Role extends BaseEntity {
|
||||
@Column({ type: "boolean", default: false })
|
||||
isAdmin: boolean;
|
||||
|
||||
// @Column({ type: "boolean", default: false })
|
||||
// isAgent: boolean;
|
||||
|
||||
@ManyToMany(() => Permission, (permission) => permission.roles)
|
||||
@JoinTable({ name: "role_permission_relation" })
|
||||
permissions: Permission[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Exclude } from "class-transformer";
|
||||
import { Column, DeleteDateColumn, Entity, JoinTable, ManyToMany, OneToMany, OneToOne } from "typeorm";
|
||||
import { Column, DeleteDateColumn, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, OneToOne } from "typeorm";
|
||||
|
||||
import { LegalUser } from "./legal-user.entity";
|
||||
import { RealUser } from "./real-user.entity";
|
||||
@@ -30,6 +30,7 @@ import { TicketMessage } from "../../tickets/entities/ticket-message.entity";
|
||||
import { Ticket } from "../../tickets/entities/ticket.entity";
|
||||
import { Wallet } from "../../wallets/entities/wallet.entity";
|
||||
import { FinancialType } from "../enums/financial-type.enum";
|
||||
import { Reseller } from "./reseller.entity";
|
||||
|
||||
@Entity()
|
||||
export class User extends BaseEntity {
|
||||
@@ -161,4 +162,13 @@ export class User extends BaseEntity {
|
||||
|
||||
@OneToMany(() => ReferralReward, (reward) => reward.user)
|
||||
referralRewards: ReferralReward[];
|
||||
|
||||
//---------------------------------------
|
||||
|
||||
@ManyToOne(() => Reseller, (reseller) => reseller.agents, { nullable: true })
|
||||
reseller?: Reseller
|
||||
|
||||
|
||||
@OneToOne(() => Reseller, reseller => reseller.owner, { nullable: true })
|
||||
ownedReseller?: Reseller
|
||||
}
|
||||
|
||||
@@ -23,5 +23,6 @@ export enum PermissionEnum {
|
||||
DMENU = "dmenu",
|
||||
DKALA = "dkala",
|
||||
DPAGE = "dpage",
|
||||
DMAIL = 'dmail'
|
||||
DMAIL = 'dmail',
|
||||
RESELLER="reseller"
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import { LegalUserRepository } from "../repositories/legal-user.repository";
|
||||
import { RealUserRepository } from "../repositories/real-user.repository";
|
||||
import { UserGroupRepository } from "../repositories/user-group.repository";
|
||||
import { UserRepository } from "../repositories/users.repository";
|
||||
import { ResellerRepository } from "../repositories/reseller.repository";
|
||||
import { CreateResellerDto } from "../DTO/create-reseller.dto";
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
private readonly logger = new Logger(UsersService.name);
|
||||
@@ -47,7 +49,8 @@ export class UsersService {
|
||||
private readonly smsService: SmsService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly referralsService: ReferralsService,
|
||||
) {}
|
||||
private readonly resellerRepository: ResellerRepository,
|
||||
) { }
|
||||
|
||||
/************************************************************ */
|
||||
|
||||
@@ -370,6 +373,29 @@ export class UsersService {
|
||||
|
||||
/************************************************************ */
|
||||
|
||||
async getResellerByUserPhone(phone: string) {
|
||||
const reseller = await this.userRepository.findOne({ where: { ownedReseller: { owner: { phone } } } });
|
||||
return { reseller };
|
||||
}
|
||||
|
||||
/************************************************************ */
|
||||
|
||||
async getAgentByUserPhone(phone: string) {
|
||||
const agent = await this.userRepository.findOne({ where: { reseller: { agents: { phone } } } });
|
||||
return { agent };
|
||||
}
|
||||
|
||||
/************************************************************ */
|
||||
|
||||
async getResellersList() {
|
||||
this.resellerRepository.find({
|
||||
relations: {
|
||||
owner: true
|
||||
}
|
||||
})
|
||||
}
|
||||
/************************************************************ */
|
||||
|
||||
private async createEmailHashLink(email: string) {
|
||||
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
|
||||
const nonce = randomBytes(16).toString("hex");
|
||||
@@ -399,4 +425,23 @@ export class UsersService {
|
||||
}
|
||||
|
||||
/************************************************************ */
|
||||
|
||||
async createReseller(dto: CreateResellerDto) {
|
||||
const { name, phone } = dto
|
||||
|
||||
const user = await this.findOneWithPhone(phone)
|
||||
|
||||
if (!user) {
|
||||
throw new BadRequestException(AuthMessage.USER_NOT_FOUND)
|
||||
}
|
||||
|
||||
const reseller = this.resellerRepository.create({
|
||||
name,
|
||||
owner: user
|
||||
})
|
||||
|
||||
await this.resellerRepository.save(reseller)
|
||||
|
||||
return reseller
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { Reseller } from "../entities/reseller.entity";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class ResellerRepository extends Repository<Reseller> {
|
||||
constructor(@InjectRepository(Reseller) resellerRepository: Repository<Reseller>) {
|
||||
super(resellerRepository.target, resellerRepository.manager, resellerRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { IUserIpAndHeaders, UserDec, UserIpAndHeaders } from "../../common/decor
|
||||
import { ParamDto } from "../../common/DTO/param.dto";
|
||||
import { RequestOtpDto } from "../auth/DTO/request-otp.dto";
|
||||
import { VerifyOtpDto } from "../auth/DTO/verify-otp.dto";
|
||||
import { CreateResellerDto } from "./DTO/create-reseller.dto";
|
||||
|
||||
@AuthGuards()
|
||||
@Controller("users")
|
||||
@@ -37,7 +38,7 @@ export class UsersController {
|
||||
private readonly usersService: UsersService,
|
||||
private readonly adminsService: AdminsService,
|
||||
private readonly customersService: CustomersService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
//----------------------- user -----------------------
|
||||
|
||||
@@ -228,4 +229,20 @@ export class UsersController {
|
||||
createRole(@Body() createDto: CreateRoleDto) {
|
||||
return this.adminsService.createRole(createDto);
|
||||
}
|
||||
|
||||
// ==================== Reseller =========
|
||||
|
||||
@ApiOperation({ summary: "create Reseller ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@Post("reseller")
|
||||
createReseller(@Body() dto: CreateResellerDto) {
|
||||
return this.usersService.createReseller(dto)
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get Resellers ==> admin route" })
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@Get("reseller")
|
||||
getResellers( ) {
|
||||
return this.usersService.getResellersList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@ import { AdminsService } from "./providers/admins.service";
|
||||
import { CustomersService } from "./providers/customers.service";
|
||||
import { RefreshTokensRepository } from "./repositories/refresh-token.repository";
|
||||
import { ReferralsModule } from "../referrals/referrals.module";
|
||||
import { ResellerRepository } from "./repositories/reseller.repository";
|
||||
import { Reseller } from "./entities/reseller.entity";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting, RealUser, LegalUser, Permission, RefreshToken]),
|
||||
TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting, RealUser, LegalUser, Permission, RefreshToken,Reseller]),
|
||||
WalletsModule,
|
||||
UtilsModule,
|
||||
AddressModule,
|
||||
@@ -52,6 +54,7 @@ import { ReferralsModule } from "../referrals/referrals.module";
|
||||
RealUserRepository,
|
||||
LegalUserRepository,
|
||||
AddressService,
|
||||
ResellerRepository
|
||||
],
|
||||
controllers: [UsersController],
|
||||
exports: [UsersService, UserRepository, CustomersService, AdminsService, RefreshTokensRepository],
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type OtpCacheKeyType = "LOGIN/REGISTER" | "FORGOT_PASSWORD" | "INVOICE_VERIFY";
|
||||
export type OtpCacheKeyType = "LOGIN/REGISTER" | "FORGOT_PASSWORD" | "INVOICE_VERIFY"|"LOGIN_RESELLER_AGENT";
|
||||
|
||||
Reference in New Issue
Block a user