1
0
forked from Yara724/api

Initial commit after migration to gitea

This commit is contained in:
2026-01-18 11:27:43 +03:30
parent a21039410c
commit ea4b8eb543
196 changed files with 45567 additions and 9 deletions

View File

@@ -0,0 +1,123 @@
import {
Body,
Controller,
Get,
Param,
Patch,
Post,
Req,
UseGuards,
} from "@nestjs/common";
import {
ApiBody,
ApiAcceptedResponse,
ApiResponse,
ApiTags,
ApiBearerAuth,
} from "@nestjs/swagger";
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
import {
ForgetPasswordSendCodeDto,
ForgetPasswordVerifyCodeDto,
} from "src/auth/dto/actor/forget-password.actor.dto";
import { LoginActorDto } from "src/auth/dto/actor/login.actor.dto";
import { ActorEditUserProfileDto } from "src/auth/dto/actor/profile.actor.dto";
import {
GenuineRegisterDto,
InsurerRegisterDto,
LegalRegisterDto,
} from "src/auth/dto/actor/register.actor.dto";
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
import { ClientKey } from "src/decorators/clientKey.decorator";
import { Roles } from "src/decorators/roles.decorator";
import { CurrentUser } from "src/decorators/user.decorator";
@Controller("actor")
@ApiTags("actor")
export class ActorAuthController {
constructor(private readonly actorAuthService: ActorAuthService) {}
@Post("register/genuine")
@ApiBody({ type: GenuineRegisterDto })
async registerGenuine(@Body() body: GenuineRegisterDto) {
return await this.actorAuthService.genuineRegister(body);
}
@Post("register/legal")
@ApiBody({ type: LegalRegisterDto })
async registerLegal(@Body() body: LegalRegisterDto) {
return await this.actorAuthService.legalRegister(body);
}
@Post("register/insurer")
@ApiBody({ type: InsurerRegisterDto })
async registerInsurer(@Body() body: InsurerRegisterDto) {
return await this.actorAuthService.insurerRegister(body);
}
@UseGuards(LocalActorAuthGuard)
@Post("login")
@Roles()
@ApiBody({
type: LoginActorDto,
description: "user verify otp -- call this api and get a tokens",
})
@ApiAcceptedResponse()
async login(@Body() body, @Req() req, @ClientKey() client) {
return await this.actorAuthService.loginActors(req.user);
}
@Post("forget-password")
@ApiBody({
type: ForgetPasswordSendCodeDto,
description: "send otp when call this api",
})
@ApiAcceptedResponse()
@ApiResponse({ type: ForgetPasswordSendCodeDto })
async forgetPassword(@Body() body: ForgetPasswordSendCodeDto) {
return await this.actorAuthService.forgetPasswordSendMail(body.email);
}
@Post("forget-password-verify")
@ApiBody({
type: ForgetPasswordVerifyCodeDto,
description: "send otp when call this api",
})
@ApiAcceptedResponse()
@ApiResponse({ type: ForgetPasswordVerifyCodeDto })
async forgetPasswordVerify(@Body() body: ForgetPasswordVerifyCodeDto) {
const { email, otp, newPassword } = body;
return await this.actorAuthService.forgetPasswordVerify(
email,
otp,
newPassword,
);
}
@Get("register/form/states/list")
async getStates() {
return await this.actorAuthService.getStates();
}
@Get("register/form/cities/list/:stateId")
async getCities(@Param("stateId") stateId: number) {
return await this.actorAuthService.getCities(stateId);
}
@Get("/profile")
@UseGuards(LocalActorAuthGuard)
@ApiBearerAuth()
async getProfile(@CurrentUser() user) {
return await this.actorAuthService.getProfiles(user);
}
@Patch("/profile")
@UseGuards(LocalActorAuthGuard)
@ApiBearerAuth()
async editProfile(
@CurrentUser() user,
@Body() update: ActorEditUserProfileDto,
) {
return await this.actorAuthService.modifyProfile(user, update);
}
}

View File

@@ -0,0 +1,45 @@
import {
Body,
Controller,
HttpException,
HttpStatus,
Post,
Req,
UseGuards,
} from "@nestjs/common";
import { ApiAcceptedResponse, ApiBody, ApiTags } from "@nestjs/swagger";
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
import { UserLoginDto } from "src/auth/dto/user/login.dto";
import { UserVerifyOtp } from "src/auth/dto/user/verify.dto";
import { LocalUserAuthGuard } from "src/auth/guards/user-local.guard";
import { CurrentUser } from "src/decorators/user.decorator";
@Controller("user")
@ApiTags("user")
export class UserAuthController {
constructor(private readonly userAuthService: UserAuthService) {}
@Post("/send-otp")
@ApiBody({
type: UserLoginDto,
description: "user login api -- call this api and send otp",
})
@ApiAcceptedResponse()
async sendOtpRq(@Body() body: UserLoginDto) {
const res = await this.userAuthService.sendOtpRequest(body.mobile);
if (res) {
throw new HttpException(res, HttpStatus.ACCEPTED);
}
}
@Post("/login")
@UseGuards(LocalUserAuthGuard)
@ApiBody({
type: UserVerifyOtp,
description: "user verify otp -- call this api and get a tokens",
})
@ApiAcceptedResponse()
async login(@Body() body, @Req() req, @CurrentUser() user) {
return await this.userAuthService.login(req.user);
}
}

View File

@@ -0,0 +1,397 @@
import { readFileSync } from "node:fs";
import {
BadGatewayException,
BadRequestException,
ForbiddenException,
Injectable,
InternalServerErrorException,
NotFoundException,
UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Types } from "mongoose";
import { ForgetPasswordVerifyCodeDtoRs } from "src/auth/dto/actor/forget-password.actor.dto";
import { ProfileActor } from "src/auth/dto/actor/profile.actor.dto";
import {
InsurerRegisterDto,
RegisterDto,
RegisterDtoRs,
} from "src/auth/dto/actor/register.actor.dto";
import { StateListDtoRs } from "src/auth/dto/actor/states.dto";
import { ClientDbService } from "src/client/entities/db-service/client.db.service";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { UserType } from "src/Types&Enums/userType.enum";
import { InsurerExpertDbService } from "src/users/entities/db-service/insurer-expert.db.service";
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
import { HashService } from "src/utils/hash/hash.service";
import { MailService } from "src/utils/mail/mail.service";
import { OtpService } from "src/utils/otp/otp.service";
function pick(obj: Record<string, any>, keys: string[]) {
const out: Record<string, any> = {};
for (const key of keys) {
if (obj[key] !== undefined) out[key] = obj[key];
}
return out;
}
// TODO FIX REGISTER TO ACTOR.SERVICE AND AUTH IN THIS MODULE
@Injectable()
export class ActorAuthService {
constructor(
private readonly jwtService: JwtService,
private readonly hashService: HashService,
private readonly expertDbService: ExpertDbService,
private readonly damageExpertDbService: DamageExpertDbService,
private readonly insurerExpertDbService: InsurerExpertDbService,
private readonly mailService: MailService,
private readonly clientDbService: ClientDbService,
private readonly otpService: OtpService,
) {}
// TODO convrt to class for dynamic controller
public async dynamicDbController(role, username, userId?: string) {
let res;
switch (role) {
case RoleEnum.EXPERT:
if (username == null && userId)
res = await this.expertDbService.findOne({
_id: new Types.ObjectId(userId),
});
else res = await this.expertDbService.findOne({ email: username });
break;
case RoleEnum.DAMAGE_EXPERT:
if (username == null && userId)
res = await this.damageExpertDbService.findOne({
_id: new Types.ObjectId(userId),
});
else
res = await this.damageExpertDbService.findOne({ email: username });
break;
case RoleEnum.COMPANY:
res = await this.insurerExpertDbService.findOne({ email: username });
break;
default:
return null;
}
return res;
}
async validateActor(username: string, pass: string, role): Promise<any> {
const user = await this.dynamicDbController(role, username);
if (user) {
if (user.role !== role) {
throw new UnauthorizedException("user not assigned to this role");
}
if (!(await this.hashService.compare(pass, user.password))) {
throw new UnauthorizedException(
"password is incorrect or access Denied",
);
} else {
return user;
}
}
return null;
}
async loginActors(user: any) {
let foundedUser = await this.dynamicDbController(user.role, user.username);
if (foundedUser) {
const payload = {
username: foundedUser.username || foundedUser.email,
sub: foundedUser._id,
fullName:
`${foundedUser.firstName || ""} ${foundedUser.lastName || ""}`.trim(),
role: foundedUser.role || "User",
userType: foundedUser.userType || "UserType",
clientKey: foundedUser.clientKey || null,
};
const accToken = this.jwtService.sign(payload, {
secret: `${process.env.SECRET}`,
expiresIn: "1h",
});
return {
...payload,
access_token: accToken,
};
} else {
throw new UnauthorizedException("expert or damage_expert not found");
}
}
async registerActors(
body: RegisterDto | InsurerRegisterDto,
userType: UserType,
) {
const hashPassword = await this.hashService.hash(body.password);
body.password = hashPassword;
(body as any).userType = userType;
try {
if ("username" in body) {
if (userType === UserType.INSURER) {
if (!body.clientKey) {
throw new BadRequestException(
"clientKey is required for this user type.",
);
}
const clientName = await this.clientDbService.find({
_id: new Types.ObjectId(body.clientKey),
});
if (!clientName) throw new NotFoundException("Client Not Found");
}
const newCompanyPayload = {
email: body.username,
password: body.password,
role: RoleEnum.COMPANY,
userType: (body as any).userType,
clientKey: body.clientKey,
firstName: body.firstName,
lastName: body.lastName,
};
return new RegisterDtoRs(
await this.insurerExpertDbService.create(newCompanyPayload),
);
} else {
if (userType === UserType.LEGAL || userType === UserType.GENUINE) {
if (!body.insuActivityCo) {
throw new BadRequestException(
"insuActivityCo is required for this user type.",
);
}
const clientName = await this.clientDbService.find({
_id: body.insuActivityCo,
});
if (!clientName) throw new NotFoundException("Client Not Found");
body.clientKey = body.insuActivityCo;
body.insuActivityCo = clientName.clientName.english;
}
switch (body.role) {
case RoleEnum.EXPERT:
return new RegisterDtoRs(await this.expertDbService.create(body));
case RoleEnum.DAMAGE_EXPERT:
return new RegisterDtoRs(
await this.damageExpertDbService.create(body),
);
default:
throw new BadRequestException(
`Invalid role for this registration type: ${body.role}`,
);
}
}
} catch (er) {
if (er.code === 11000) {
throw new BadRequestException(
"A user with these details already exists.",
);
} else if (er.errors) {
const errObjKey = Object.keys(er.errors)[0];
const { path, kind } = er.errors[errObjKey];
throw new BadRequestException(
`Validation failed for field '${path}'. Expected a valid ${kind}.`,
);
} else {
throw er;
}
}
}
async legalRegister(body: RegisterDto) {
return await this.registerActors(body, UserType.LEGAL);
}
async genuineRegister(body: RegisterDto) {
return await this.registerActors(body, UserType.GENUINE);
}
async insurerRegister(body: InsurerRegisterDto) {
Object.assign(body, { role: RoleEnum.COMPANY });
return await this.registerActors(body, UserType.INSURER);
}
/// TODO need to seed
async getStates() {
const states = JSON.parse(
readFileSync(`${process.cwd()}/src/static/states.json`, "utf8"),
);
return new StateListDtoRs(states);
}
/// TODO need to seeds
async getCities(id: number) {
const cities = JSON.parse(
readFileSync(`${process.cwd()}/src/static/cities.json`, "utf8"),
);
const citiesList = cities.filter((c) => c.province_id == id);
return new StateListDtoRs(citiesList);
}
async forgetPasswordSendMail(email: string) {
const normalizedEmail = email.toLowerCase().trim();
const filter = { email: normalizedEmail };
let actor;
// expert
actor = await this.expertDbService.findOne(filter);
let dbServiceToUpdate = this.expertDbService;
if (!actor) {
actor = (await this.damageExpertDbService.findOne(filter)) as any;
dbServiceToUpdate = this.damageExpertDbService as any;
}
if (!actor) {
actor = await this.insurerExpertDbService.findOne(filter);
dbServiceToUpdate = this.insurerExpertDbService as any;
}
if (!actor) {
throw new NotFoundException("actor not found");
}
const otp = this.otpService.create();
const sendEmail = await this.mailService.sendMail(normalizedEmail, otp);
if (sendEmail) {
const hashOtp = await this.hashService.hash(otp);
await dbServiceToUpdate.findOneAndUpdate(filter, { otp: hashOtp });
return sendEmail;
} else {
throw new BadGatewayException("mailServer in unavailable");
}
}
async forgetPasswordVerify(email, otp, newPassword) {
const userExist = await this.expertDbService.findOne({ email });
if (!userExist) throw new NotFoundException("user not found");
const decodeOtp = await this.hashService.compare(otp, userExist.otp);
if (!decodeOtp) throw new UnauthorizedException("otp invalid");
if (decodeOtp) {
const hashNewPassword = await this.hashService.hash(newPassword);
await this.expertDbService.findOneAndUpdate(
{ email },
{ password: hashNewPassword },
);
return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
}
}
async getProfiles(currentUser) {
const userDetail = await this.dynamicDbController(
currentUser.role,
null,
currentUser.sub,
);
return new ProfileActor(userDetail);
}
async modifyProfile(currentUser: any, update: Record<string, any>) {
const role = currentUser?.role;
const userId = currentUser?.sub;
if (!role || !userId) {
throw new BadRequestException("Invalid actor payload");
}
const allowedFieldsByRole: Record<string, string[]> = {
expert: [
"fullName",
"firstName",
"lastName",
"email",
"phone",
"city",
"state",
"bio",
"nationalCode",
"birthDate",
"avatarUrl",
"address",
],
damage_expert: [
"fullName",
"email",
"phone",
"companyName",
"city",
"state",
"bio",
"nationalCode",
"birthDate",
"avatarUrl",
"address",
],
};
const allowedFields = allowedFieldsByRole[role];
if (!allowedFields) {
throw new ForbiddenException("Profile update not allowed for this role");
}
const sanitized = Object.fromEntries(
Object.entries(update).filter(([key]) => allowedFields.includes(key)),
);
if (Object.keys(sanitized).length === 0) {
throw new BadRequestException("No valid fields to update");
}
if (sanitized.birthDate && typeof sanitized.birthDate === "string") {
const parsed = new Date(sanitized.birthDate);
if (isNaN(parsed.getTime())) {
throw new BadRequestException("birthDate must be a valid date");
}
sanitized.birthDate = parsed;
}
// fetch user detail (document or plain object)
const document = await this.dynamicDbController(role, null, userId);
if (!document) throw new NotFoundException("Profile not found");
try {
// if its a mongoose document (has .save)
if (typeof document.save === "function") {
Object.assign(document, sanitized);
await document.save();
return document;
}
// else, update directly via corresponding DB service
switch (role) {
case "expert":
await this.expertDbService.updateOne(
{ _id: new Types.ObjectId(userId) },
{ $set: sanitized },
);
break;
case "damage_expert":
await this.damageExpertDbService.updateOne(
{ _id: new Types.ObjectId(userId) },
{ $set: sanitized },
);
break;
default:
throw new ForbiddenException("Unsupported role for update");
}
// return the fresh document
return await this.dynamicDbController(role, null, userId);
} catch (err) {
throw new InternalServerErrorException("Failed to update profile");
}
}
}

View File

@@ -0,0 +1,137 @@
import {
HttpException,
HttpStatus,
Injectable,
Logger,
NotAcceptableException,
NotFoundException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Types } from "mongoose";
import { LoginDtoRs } from "src/auth/dto/user/login.dto";
import { UserDbService } from "src/users/entities/db-service/user.db.service";
import { HashService } from "src/utils/hash/hash.service";
import { OtpService } from "src/utils/otp/otp.service";
import { SmsManagerService } from "src/utils/sms-manager/sms-manager.service";
// TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE
@Injectable()
export class UserAuthService {
private readonly logger = new Logger(UserAuthService.name);
constructor(
private readonly jwtService: JwtService,
private readonly userDbService: UserDbService,
private readonly hashService: HashService,
private readonly otpCreator: OtpService,
private readonly smsManagerService: SmsManagerService,
) {}
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.userDbService.findOne({ username });
if (!user) throw new NotFoundException("user not found");
const now = new Date().getTime();
if (user.otp == null) throw new NotAcceptableException("please get otp");
if (user.otpExpire < now) {
throw new NotAcceptableException("expire otp");
}
if (await this.hashService.compare(pass, user.otp)) {
return user;
}
return false;
}
async login(user: any) {
const payload = {
username: user.username,
sub: user.id,
role: "user",
};
const accToken = this.jwtService.sign(payload, {
secret: `${process.env.SECRET}`,
});
await this.userDbService.findOneAndUpdate(
{ username: user.username },
{
tokens: { token: accToken },
otp: null,
},
);
return {
userId: user._id,
access_token: accToken,
};
}
async sendOtpRequest(mobile: string): Promise<LoginDtoRs> {
const userExist = await this.userDbService.findOne({
mobile,
});
const otp = this.otpCreator.create();
const hashOtp = await this.hashService.hash(otp);
if (!userExist) {
await this.smsSender(otp, mobile);
/// create otp request
const newUser = await this.userDbService.createUser({
mobile,
username: mobile,
otp: hashOtp,
tokens: {
token: "",
rfToken: "",
},
fullName: "",
nationalCode: "",
lastLogin: new Date(),
clientKey: new Types.ObjectId(),
birthDay: "",
city: "",
address: "",
state: "",
otpExpire: new Date(
new Date().getTime() + +process.env.EXP_OTP_TIME * 60 * 1000,
).getTime(),
});
return new LoginDtoRs(newUser);
}
if (userExist) {
await this.smsSender(otp, mobile);
const updateTokens = await this.userDbService.findOneAndUpdate(
{
username: userExist.username,
},
{
otp: hashOtp,
otpExpire: new Date(
new Date().getTime() + +process.env.EXP_OTP_TIME * 60 * 1000,
).getTime(),
},
);
if (updateTokens) return new LoginDtoRs(userExist);
}
}
private async smsSender(otp: string, mobile: string) {
return this.smsManagerService
.verifyLookUp({
token: otp,
template: process.env.AUTH_SMS_TEMPLATE,
receptor: mobile,
})
.then((smsRes) => {
this.logger.log(
`${"phone : " + mobile + " " + ", status : " + smsRes["return"].status + ", otp : " + otp} `,
);
})
.catch((er) => {
this.logger.error(
`${"phone : " + mobile + " " + ", status : " + er["return"].status + ", otp : " + otp} `,
);
throw new HttpException(
" auth sms send failed",
HttpStatus.INTERNAL_SERVER_ERROR,
);
});
}
}

42
src/auth/auth.module.ts Normal file
View File

@@ -0,0 +1,42 @@
import { Module } from "@nestjs/common";
import { JwtModule, JwtService } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { LocalStrategy } from "src/auth/stratregys/local.strategy";
import { LocalActorStrategy } from "src/auth/stratregys/local-actor.strategy";
import { ActorAuthController } from "src/auth/auth-controllers/actor/actor.auth.controller";
import { UserAuthController } from "src/auth/auth-controllers/user/user.auth.controller";
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
import { ClientModule } from "src/client/client.module";
import { UsersModule } from "src/users/users.module";
import { HashModule } from "src/utils/hash/hash.module";
import { MailModule } from "src/utils/mail/mail.module";
import { OtpModule } from "src/utils/otp/otp.module";
import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module";
@Module({
imports: [
MailModule,
UsersModule,
ClientModule,
HashModule,
OtpModule,
PassportModule,
SmsManagerModule,
JwtModule.register({
signOptions: { expiresIn: "1h" },
global: true,
secret: `${process.env.SECRET}`,
}),
],
providers: [
UserAuthService,
ActorAuthService,
LocalStrategy,
LocalActorStrategy,
JwtService,
],
exports: [LocalStrategy, UserAuthService, ActorAuthService, JwtService],
controllers: [UserAuthController, ActorAuthController],
})
export class AuthModule {}

View File

@@ -0,0 +1,30 @@
import { ApiProperty } from "@nestjs/swagger";
export class ForgetPasswordSendCodeDto {
@ApiProperty({ example: "balali.arash@gmail.com", type: String })
email: string;
}
export class ForgetPasswordVerifyCodeDto {
@ApiProperty({ example: "09331009989", type: String })
email: string;
@ApiProperty({ type: String, examples: { true: "22222" } })
otp: string;
@ApiProperty({ type: String })
newPassword: string;
}
export class ForgetPasswordVerifyCodeDtoRs {
@ApiProperty({ example: "balali.arash@gmail.com", type: String })
email: string;
@ApiProperty({ type: String, examples: { true: "22222" } })
message: string;
constructor(user, message) {
this.email = user.email;
this.message = message;
}
}

View File

@@ -0,0 +1,41 @@
import { ApiProperty } from "@nestjs/swagger";
import { RoleEnum } from "src/Types&Enums/role.enum";
export class LoginActorDto {
@ApiProperty({ example: RoleEnum, type: "array", description: "LOGIN_DTO" })
role: RoleEnum[];
@ApiProperty({})
username: string;
@ApiProperty({})
password: string;
}
export class LoginActorDtoRs extends LoginActorDto {
private readonly userId;
constructor(userData) {
super();
this.userId = userData._id;
this.role = userData.role;
this.username = userData.email;
this.clientKey = userData.clientKey;
this.token = userData.token;
this.refreshToken = userData.refreshToken;
}
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
fullName: string;
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
role: RoleEnum[];
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
token: string;
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
refreshToken: string;
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
clientKey: string;
}

View File

@@ -0,0 +1,98 @@
import { ApiProperty, ApiSchema } from "@nestjs/swagger";
import { IsMobilePhone, IsString, Length } from "class-validator";
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
export class ProfileActor {
public email: string;
public fullName: string;
public nationalCode: string;
public insuActivityCo: string;
public userType: string;
public mobile: string;
public address: string;
public state: string;
public city: string;
constructor(Profile: DamageExpertModel) {
this.email = Profile?.email;
this.fullName = Profile?.firstName + " " + Profile?.lastName;
this.nationalCode = Profile?.nationalCode;
this.insuActivityCo = Profile?.insuActivityCo;
this.userType = Profile?.userType;
this.mobile = Profile?.mobile;
this.address = Profile?.address;
this.state = Profile?.state;
this.city = Profile?.city;
}
}
@ApiSchema({
name: "Edit Actor Profile",
description: "Body of the request to edit the actor's profile",
})
export class ActorEditUserProfileDto {
@ApiProperty({
type: "string",
description: "First name of the actor",
example: "heshmat",
nullable: true,
})
@IsString()
@Length(2, 40)
firstName?: string;
@ApiProperty({
type: "string",
description: "Last name of the actor",
example: "Heshmati",
nullable: true,
})
@IsString()
@Length(1, 10)
lastName?: string;
@ApiProperty({
type: "string",
description: "Mobile phone of the actor",
example: "09123456789",
nullable: true,
})
@IsMobilePhone("fa-IR")
mobile?: string;
@ApiProperty({
type: "string",
description: "تلفن خط ثابت",
example: "02122222222",
nullable: true,
})
@IsString()
phone?: string;
@ApiProperty({
type: "string",
description: "City of the user",
example: "استان",
nullable: true,
})
@IsString()
city?: string;
@ApiProperty({
type: "string",
description: "State of the user",
example: "شهر",
nullable: true,
})
@IsString()
state?: string;
@ApiProperty({
type: "string",
description: "Address of the user",
example: "آدرس",
nullable: true,
})
@IsString()
address?: string;
}

View File

@@ -0,0 +1,341 @@
import { ApiProperty } from "@nestjs/swagger";
import { Exclude } from "class-transformer";
import { IsEmail } from "class-validator";
import { Types } from "mongoose";
import { Degrees } from "src/Types&Enums/degrees.enum";
import {
ExpertizedAtEnum,
PreviousWorkEnum,
SkillEnum,
} from "src/Types&Enums/damage-expert.enum";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { UserType } from "src/Types&Enums/userType.enum";
@Exclude()
export class RegisterDto {
clientKey?: string;
@ApiProperty({
example: "Soheil",
type: "string",
description: "firstname of actor",
})
firstName: string;
@ApiProperty({
enum: RoleEnum,
examples: RoleEnum,
type: "string",
description: "firstname of actor",
})
role: RoleEnum;
userType: UserType;
@ApiProperty({
example: "Hajizadeh",
type: "string",
description: "lastname of actor",
})
lastName: string;
@ApiProperty({
example: "4311402422",
type: "string",
description: "nationalCode of actor",
})
nationalCode: string;
@ApiProperty({
example: "dev.callmeskylark@gmail.com",
type: "string",
description: "email of actor",
})
email: string;
@ApiProperty({
example: "123321",
type: "string",
description: "password of actor",
})
password: string;
@ApiProperty({
example: "7522312365495123",
type: "string",
description: "sheba of actor",
})
sheba?: string;
@ApiProperty({
example: "02133564521",
type: "string",
description: "phone of actor",
})
phone?: string;
@ApiProperty({
example: "09226187419",
type: "string",
description: "mobile of actor",
})
mobile: string;
@ApiProperty({
example: "Tehran",
type: "string",
description: "province of actor",
})
state?: string;
@ApiProperty({
example: "Tehran",
type: "string",
description: "city of actor",
})
city?: string;
@ApiProperty({
example: "Gisha , No 7",
type: "string",
description: "address of actor",
})
address?: string;
@ApiProperty({
enum: Degrees,
examples: Degrees,
type: String,
description: "expDegree of actor",
})
expDegree?: Degrees;
@ApiProperty({
example: "5",
type: "number",
description: "insuActivityTime of actor",
})
insuActivityTime?: number;
@ApiProperty({
example: "64fc4978b74d670939b08920",
type: String,
description: "insuActivityCo of actor",
})
insuActivityCo: string;
@ApiProperty({
example: "5",
type: "number",
description: "policeActivityTime of actor",
})
policeActivityTime?: number;
@ApiProperty({
examples: ["headquarters", "queue"],
type: "string",
description: "policeActivityKind of actor",
})
policeActivityKind?: string;
@ApiProperty({
example: "Tehran",
type: String,
description: "policeServicePlace of actor",
})
policeServicePlace?: string;
}
export class GenuineRegisterDto extends RegisterDto {
@ApiProperty({
type: () => ({
fullName: { type: "string", example: "John Doe" },
nationalCode: { type: "string", example: "4311402422" },
dob: {
type: "string",
example: "1990-01-01",
description: "date of birth in ISO string or yyyy-mm-dd",
},
mobile: { type: "string", example: "09226187419" },
}),
description: "personal information of damage expert (duplicated with some flat fields)",
required: false,
})
personalInfo?: {
fullName: string;
nationalCode: string;
dob: string;
mobile: string;
};
@ApiProperty({
type: () => ({
nationalCard: {
type: "string",
example: "665f0e5ab74d670939b08920",
description: "fileId of uploaded national card",
},
selfie: {
type: "string",
example: "665f0e5ab74d670939b08921",
description: "fileId of uploaded selfie",
},
activityCert: {
type: "string",
example: "665f0e5ab74d670939b08922",
description: "fileId of uploaded activity certificate",
},
}),
description:
"IDs of already uploaded documents in upload module (no files uploaded directly here)",
required: false,
})
personalDocs?: {
nationalCard?: string;
selfie?: string;
activityCert?: string;
};
@ApiProperty({
type: () => ({
state: {
type: "number",
example: 8,
description: "state id from /actor/register/form/states/list",
},
city: {
type: "number",
example: 101,
description: "city id from /actor/register/form/cities/list/:stateId",
},
expertizedAt: {
type: "array",
items: { enum: Object.values(ExpertizedAtEnum) },
example: [ExpertizedAtEnum.BADANE, ExpertizedAtEnum.FANI],
description: "one or more expertized fields",
},
}),
required: false,
})
workExperience?: {
state: number;
city: number;
expertizedAt: ExpertizedAtEnum[];
};
@ApiProperty({
type: () => ({
workingYears: {
type: "number",
example: 5,
},
casesCount: {
type: "number",
example: 120,
},
previousWork: {
enum: PreviousWorkEnum,
example: PreviousWorkEnum.INSURANCE_COMPANY,
},
}),
required: false,
})
workRecords?: {
workingYears: number;
casesCount: number;
previousWork: PreviousWorkEnum;
};
@ApiProperty({
type: "array",
items: {
type: "string",
enum: Object.values(SkillEnum),
},
required: false,
description: "one or more skills of the damage expert",
example: [SkillEnum.SMOOTHING, SkillEnum.SCENE_EXPERT],
})
skills?: SkillEnum[];
@ApiProperty({
type: () => ({
IBAN: {
type: "string",
example: "IR820540102680020817909002",
},
cardNum: {
type: "string",
example: "6037991234567890",
},
accountNum: {
type: "string",
example: "0101234567000",
},
}),
required: false,
})
financialInfo?: {
IBAN: string;
cardNum: string;
accountNum: string;
};
}
export class LegalRegisterDto implements RegisterDto {
userType: UserType;
@ApiProperty()
firstName: string;
@ApiProperty()
role: RoleEnum;
@ApiProperty()
lastName: string;
@ApiProperty()
nationalCode: string;
@ApiProperty()
email: string;
@ApiProperty()
password: string;
@ApiProperty()
mobile: string;
@ApiProperty({
example: "64fc4978b74d670939b08920",
type: String,
description: "insuActivityCo of actor",
})
insuActivityCo: string;
}
export class InsurerRegisterDto {
@ApiProperty()
firstName: string;
@ApiProperty()
lastName: string;
@ApiProperty({ description: "just submit by Email" })
@IsEmail()
username: string;
@ApiProperty()
password: string;
@ApiProperty({ example: "{saman : 651abe5814ed4bb6ee20cd81}" })
clientKey: Types.ObjectId;
}
export class RegisterDtoRs extends RegisterDto {
@ApiProperty({ type: "string", description: "REGISTER_DTO_RS" })
message: string;
constructor(registerData) {
super();
this.message = registerData.message || "ثبت نام با موفقیت انجام شد.";
}
}

View File

@@ -0,0 +1,15 @@
export class StatesDtoRs {
name: string;
id: string;
constructor(states) {
this.name = states.name;
this.id = states.id;
}
}
export class StateListDtoRs {
list: StatesDtoRs[];
constructor(statesLis: []) {
this.list = statesLis.map((s) => new StatesDtoRs(s));
}
}

View File

View File

@@ -0,0 +1,44 @@
import { ApiProperty } from "@nestjs/swagger";
import { UserModel } from "src/users/entities/schema/user.schema";
export class UserLoginDto {
@ApiProperty({
example: "09226187419",
type: "string",
description: "User login dto",
})
mobile: string;
}
export class LoginDtoRs extends UserModel {
message: string;
@ApiProperty({
example: "09226187419",
type: "string",
description: "User login dto",
})
tokens: { token: string; rfToken: string };
@ApiProperty({
example: "09226187419",
type: "string",
description: "User login dto",
})
userId: string;
@ApiProperty({
example: "09226187419",
type: "string",
description: "User login dto",
})
firstName: string;
lastName: string;
username: string;
mobile: string;
nationalCode: string;
constructor(loginData) {
super();
this.mobile = loginData.mobile;
}
}

View File

@@ -0,0 +1,17 @@
import { ApiProperty } from "@nestjs/swagger";
export class UserVerifyOtp {
@ApiProperty({
example: "09226187419",
type: "string",
description: "User login dto",
})
username: string;
@ApiProperty({
example: "258567",
type: "string",
description: "User login verify dto",
})
password: string;
}

View File

@@ -0,0 +1,63 @@
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { AuthGuard } from "@nestjs/passport";
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
import { RoleEnum } from "src/Types&Enums/role.enum";
@Injectable()
export class LocalActorAuthGuard extends AuthGuard("actor") {
constructor(
private readonly actorAuthService: ActorAuthService,
private readonly jwtService: JwtService,
) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
const path = request.url;
if (!token) {
if (path === "/actor/login") {
const loginData = await this.actorAuthService.loginActors(request.body);
request.user = loginData;
request.identity = request;
return true;
} else {
throw new UnauthorizedException("Token not found");
}
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: `${process.env.SECRET}`,
});
if (
![RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT, RoleEnum.COMPANY].includes(
payload.role,
)
) {
throw new UnauthorizedException("User role is not authorized");
}
request.user = payload;
request.identity = request.user;
} catch {
throw new UnauthorizedException("Invalid token");
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
//@ts-ignore
const [type, token] = request.headers.authorization?.split(" ") ?? [];
return type === "Bearer" ? token : undefined;
}
}

View File

@@ -0,0 +1,127 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
ForbiddenException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { ClaimRequestManagementDbService } from "src/claim-request-management/entites/db-service/claim-request-management.db.service";
import { Types } from "mongoose";
/**
* Guard that allows:
* - Users to access their own claim files
* - Experts to access IN_PERSON claim files they initiated
*/
@Injectable()
export class ClaimAccessGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private readonly claimDbService: ClaimRequestManagementDbService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: `${process.env.SECRET}`,
});
// Allow users to pass through (they will be checked by service methods)
if (payload.role === RoleEnum.USER) {
request.user = payload;
request.identity = request.user;
return true;
}
// For experts, check if they're accessing an IN_PERSON claim file they initiated
if (
payload.role === RoleEnum.EXPERT ||
payload.role === RoleEnum.DAMAGE_EXPERT
) {
// Extract claimRequestId from route params
const claimRequestId =
request.params?.claimRequestId ||
request.params?.claimRequestID ||
request.params?.id;
if (!claimRequestId) {
// If no claim ID in params, allow access (e.g., for listing endpoints)
// The service will filter appropriately
request.user = payload;
request.actor = payload;
request.identity = payload;
return true;
}
// Verify expert has access to this specific claim file
const hasAccess = await this.verifyExpertClaimAccess(
claimRequestId,
payload.sub,
);
if (!hasAccess) {
throw new ForbiddenException(
"You can only access IN_PERSON claim files that you initiated",
);
}
request.user = payload;
request.actor = payload;
request.identity = payload;
return true;
}
throw new UnauthorizedException("Invalid role");
} catch (error) {
if (error instanceof ForbiddenException || error instanceof UnauthorizedException) {
throw error;
}
throw new UnauthorizedException();
}
}
private async verifyExpertClaimAccess(
claimRequestId: string,
expertId: string,
): Promise<boolean> {
try {
const claim = await this.claimDbService.findOne(claimRequestId);
if (!claim) {
return false;
}
const blameFile = claim.blameFile;
if (!blameFile) {
return false;
}
// Check if it's an expert-initiated IN_PERSON file
if (
blameFile.expertInitiated &&
blameFile.creationMethod === "IN_PERSON" &&
blameFile.initiatedBy
) {
// Verify the expert is the one who initiated it
return String(blameFile.initiatedBy) === expertId;
}
return false;
} catch (error) {
return false;
}
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(" ") ?? [];
return type === "Bearer" ? token : undefined;
}
}

View File

@@ -0,0 +1,44 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Request } from "express";
import { RoleEnum } from "src/Types&Enums/role.enum";
@Injectable()
export class GlobalGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: `${process.env.SECRET}`,
});
if (payload.role !== RoleEnum.USER) {
console.log(
"🚀 ~ GlobalGuard ~ canActivate ~ request.user.role:",
request.user.role,
);
throw new UnauthorizedException();
}
request.user = payload;
request.identity = request.user;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(" ") ?? [];
return type === "Bearer" ? token : undefined;
}
}

View File

@@ -0,0 +1,24 @@
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// get the roles required
const roles = this.reflector.getAllAndOverride<string[]>("role", [
context.getHandler(),
context.getClass(),
]);
if (!roles) {
return false;
}
const request = context.switchToHttp().getRequest();
const userRoles = request.user?.role?.split(",");
return this.validateRoles(roles, userRoles);
}
validateRoles(roles: string[], userRoles: string[]) {
return roles.some((role) => userRoles.includes(role));
}
}

View File

@@ -0,0 +1,27 @@
import {
ExecutionContext,
Injectable,
NotAcceptableException,
} from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
@Injectable()
export class LocalUserAuthGuard extends AuthGuard("local") {
constructor(private readonly userAuthService: UserAuthService) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const { username, password } = request.body;
let isValidUser = await this.userAuthService.validateUser(
username,
password,
);
if (!isValidUser) {
throw new NotAcceptableException("otp is wrong");
}
request["user"] = isValidUser;
return true;
}
}

View File

@@ -0,0 +1,22 @@
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local";
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
@Injectable()
export class LocalActorStrategy extends PassportStrategy(Strategy, "actor") {
constructor(private readonly actorAuthService: ActorAuthService) {
super();
}
// async validate(username, password): Promise<any> {
// const user = await this.actorAuthService.validateActor(
// username,
// password,
// );
// if (!user) {
// throw new UnauthorizedException("user not found");
// }
// return user;
// }
}

View File

@@ -0,0 +1,19 @@
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local";
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly userAuthService: UserAuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.userAuthService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException("user not found please register");
}
return user;
}
}