Files
yara724api/src/auth/auth-services/actor.auth.service.ts
2026-01-21 16:45:34 +03:30

421 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
phone: body.phone,
mobile:body.mobile,
city: body.city,
state: body.state,
address: body.address,
};
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,
currentUser.role === "company" ? currentUser.username : 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",
"firstName",
"lastName",
"email",
"phone",
"companyName",
"city",
"state",
"bio",
"nationalCode",
"birthDate",
"avatarUrl",
"address",
],
company: [
"firstName",
"lastName",
"phone",
"mobile",
"city",
"state",
"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, currentUser.role === "company" ? currentUser.username : 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;
case "company":
await this.insurerExpertDbService.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");
}
}
}