Files
dsc-api/src/modules/users/providers/users.service.ts
T
2025-02-18 12:20:22 +03:30

588 lines
21 KiB
TypeScript

import { createHmac } from "node:crypto";
import { BadRequestException, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { FastifyReply } from "fastify";
import slugify from "slugify";
import { In, Not, QueryRunner } from "typeorm";
import {
AdminMessage,
AdsMessage,
CommonMessage,
LegalUserMessage,
RealUserMessage,
UserMessage,
} from "../../../common/enums/message.enum";
import { AddressService } from "../../address/providers/address.service";
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
import { UserSettingsService } from "../../settings/providers/user-settings.service";
import { EmailService } from "../../utils/providers/email.service";
import { PaginationUtils } from "../../utils/providers/pagination.utils";
import { PasswordService } from "../../utils/providers/password.service";
import { WalletsService } from "../../wallets/providers/wallets.service";
import { CheckValidityDTO } from "../DTO/check-validity.dto";
import { CreateAdminDto } from "../DTO/create-admin.dto";
import { CreateLegalUserDto } from "../DTO/create-legal-user.dto";
import { CreateRealUserDto } from "../DTO/create-real-user.dto";
import { CreateRoleDto } from "../DTO/create-role.dto";
import { CreateCustomerDto } from "../DTO/create-user.dto";
import { SearchAdminQueryDto } from "../DTO/search-admins-query.dto";
import { SearchCustomersDto } from "../DTO/search-customers.dto";
import { SearchRolesQueryDto } from "../DTO/search-roles.dto";
import { UpdateEmailDto } from "../DTO/update-email.dto";
import { UpdateProfileDto } from "../DTO/update-profile.dto";
import { CreateUserGroupDto } from "../DTO/user-group.dto";
import { EmailVerifyQueryDto } from "../DTO/verify-email-query.dto";
import { Role } from "../entities/role.entity";
import { User } from "../entities/user.entity";
import { RoleEnum } from "../enums/role.enum";
import { ValidityType } from "../enums/validity-type.enum";
import { LegalUserRepository } from "../repositories/legal-user.repository";
import { PermissionsRepository } from "../repositories/permissions.repository";
import { RealUserRepository } from "../repositories/real-user.repository";
import { RoleRepository } from "../repositories/roles.repository";
import { UserGroupRepository } from "../repositories/user-group.repository";
import { UserRepository } from "../repositories/users.repository";
@Injectable()
export class UsersService {
constructor(
private readonly userRepository: UserRepository,
private readonly userGroupRepository: UserGroupRepository,
private readonly userSettingsService: UserSettingsService,
private readonly walletsService: WalletsService,
private readonly rolesRepository: RoleRepository,
private readonly permissionsRepository: PermissionsRepository,
private readonly passwordService: PasswordService,
private readonly realUserRepository: RealUserRepository,
private readonly legalUserRepository: LegalUserRepository,
private readonly addressService: AddressService,
private readonly emailService: EmailService,
private readonly configService: ConfigService,
) {}
/************************************************************ */
async findOneById(id: string) {
const user = await this.userRepository.findOneBy({ id });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return { user };
}
/************************************************************ */
async findOneByIdWithQueryRunner(id: string, queryRunner: QueryRunner) {
const user = await queryRunner.manager.findOneBy(User, { id });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return user;
}
/************************************************************ */
async getMe(userId: string) {
const user = await this.userRepository.findOne({ where: { id: userId }, relations: { roles: true } });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return { user };
}
/************************************************************ */
async getMyFinancialInfo(userId: string) {
const user = await this.userRepository
.createQueryBuilder("user")
.leftJoinAndSelect("user.address", "address")
.leftJoinAndSelect("address.city", "city")
.leftJoinAndSelect("city.province", "province")
.leftJoinAndSelect("user.realUser", "realUser")
.leftJoinAndSelect("user.legalUser", "legalUser")
.where("user.id = :id", { id: userId })
.getOne();
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return { user };
}
/************************************************************ */
async updateProfile(userId: string, updateProfileDto: UpdateProfileDto) {
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
//
if (updateProfileDto.userName) {
updateProfileDto.userName = slugify(updateProfileDto.userName, { lower: true, trim: true });
const existUserName = await this.userRepository.findOneBy({ userName: updateProfileDto.userName, id: Not(userId) });
if (existUserName) throw new BadRequestException(UserMessage.USERNAME_EXIST);
}
await this.userRepository.save({ ...user, ...updateProfileDto });
//
return {
message: CommonMessage.UPDATE_SUCCESS,
};
}
/************************************************************ */
async updateEmail(userId: string, updateDto: UpdateEmailDto) {
const { email } = updateDto;
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
//
const existEmail = await this.userRepository.findOneBy({ email, id: Not(userId) });
if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST);
//
const emailLink = await this.createEmailHashLink(user.id);
await this.emailService.sendVerificationEmail(email, emailLink, `${user.firstName} ${user.lastName}`);
await this.userRepository.save({ ...user, email });
return {
message: UserMessage.VERIFY_EMAIL_LINK_SENT,
email,
};
}
/************************************************************ */
//TODO:fix this later
async verifyEmail(queryDto: EmailVerifyQueryDto, rep: FastifyReply) {
const { token, userId, ts } = queryDto;
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
// Check if timestamp is within allowed time window (e.g., 24 hours)
const timestamp = parseInt(ts, 10);
if (Date.now() - timestamp > 24 * 60 * 60 * 1000) {
throw new BadRequestException(UserMessage.EXPIRED_EMAIL_TOKEN);
}
const payload = `${user.id}:${timestamp}`;
const expectedHash = createHmac("sha256", secret).update(payload).digest("hex");
if (expectedHash !== token) throw new BadRequestException(UserMessage.INVALID_EMAIL_TOKEN);
user.emailVerified = true;
await this.userRepository.save(user);
// return { message: UserMessage.EMAIL_VERIFIED };
return rep.redirect("https://google.com");
}
/************************************************************ */
async findOneWithEmail(email: string): Promise<User | null> {
const user = await this.userRepository.findOneWithEmail(email);
return user;
}
/************************************************************ */
async findOneWithPhone(phone: string): Promise<User | null> {
const user = await this.userRepository.findOneWithPhone(phone);
return user;
}
/************************************************************ */
async createUser(registerDto: CompleteRegistrationDto, hashedPassword: string, queryRunner: QueryRunner): Promise<User> {
const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER });
if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND);
const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode });
if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
const user = queryRunner.manager.create(User, {
...registerDto,
password: hashedPassword,
roles: [role],
});
await queryRunner.manager.save(user);
await this.userSettingsService.createUserSettings(user.id, queryRunner);
await this.walletsService.createUserWallet(user.id, queryRunner);
return user;
}
/************************************************************ */
async createAdmin(createDto: CreateAdminDto) {
const existEmail = await this.userRepository.findOneBy({ email: createDto.email });
if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST);
const role = await this.rolesRepository.findOne({ where: { id: createDto.roleId }, relations: { permissions: true } });
if (!role) throw new BadRequestException(AdsMessage.ROLE_NOT_FOUND);
await this.rolesRepository.save(role);
if (createDto.password !== createDto.repeatPassword) throw new BadRequestException(AdminMessage.PASSWORD_NOT_MATCH);
const hashedPassword = await this.passwordService.hashPassword(createDto.password);
const userName = slugify(`${createDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true });
const adminUser = this.userRepository.create({ ...createDto, roles: [role], password: hashedPassword, userName });
await this.userRepository.save(adminUser);
return {
message: AdminMessage.ADMIN_CREATED,
adminUser,
};
}
/************************************************************ */
async createRole(createDto: CreateRoleDto) {
const existRole = await this.rolesRepository.findOne({ where: { name: createDto.name } });
if (existRole) throw new BadRequestException(AdminMessage.ROLE_EXIST);
const permissions = await this.permissionsRepository.findBy({ id: In(createDto.permissions) });
if (permissions.length !== createDto.permissions.length) throw new BadRequestException(AdminMessage.PERMISSIONS_NOT_FOUND);
const role = this.rolesRepository.create({ ...createDto, permissions, isAdmin: true });
await this.rolesRepository.save(role);
return {
message: AdminMessage.ROLE_CREATED,
role,
};
}
/************************************************************ */
async checkValidity(checkDto: CheckValidityDTO, userId: string) {
const { type } = checkDto;
if (type === ValidityType.USERNAME) checkDto.value = slugify(checkDto.value, { lower: true, trim: true });
const user = await this.userRepository.findOneBy({ [type]: checkDto.value, id: Not(userId) });
if (user) throw new BadRequestException(UserMessage.USER_EXISTS);
return { message: CommonMessage.VALID_FOR_CHOOSE };
}
/************************************************************ */
async createUserGroup(createUserGroupDto: CreateUserGroupDto) {
const users = await this.userRepository.find({ where: { id: In(createUserGroupDto.userIds) } });
const userGroup = this.userGroupRepository.create({
...createUserGroupDto,
users,
});
await this.userGroupRepository.save(userGroup);
return {
message: UserMessage.USER_GROUP_CREATED,
};
}
///****************************************************** */
async getUserGroups() {
const usersGroup = await this.userGroupRepository.find({ relations: { users: true } });
return {
usersGroup,
};
}
///****************************************************** */
async getUserGroupById(id: string) {
const userGroup = await this.userGroupRepository.findGroupById(id);
if (!userGroup) throw new BadRequestException(UserMessage.USER_GROUP_NOT_FOUND);
return {
userGroup,
};
}
///****************************************************** */
async getUserGroupsByIds(ids: string[]) {
const userGroups = await this.userGroupRepository.find({ where: { id: In(ids) } });
if (!userGroups) throw new BadRequestException(UserMessage.USER_GROUP_NOT_FOUND);
return {
userGroups,
};
}
///****************************************************** */
async updateUserPassword(userId: string, newPassword: string) {
const updatedUser = await this.userRepository.update(
{ id: userId },
{
password: newPassword,
},
);
return {
updatedUser,
};
}
/************************************************************ */
async findAllUsers() {
const users = await this.userRepository.find({
relations: ["groups"],
});
return { users };
}
/************************************************************ */
async findAllCustomers(queryDto: SearchCustomersDto) {
const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.userRepository
.createQueryBuilder("user")
.leftJoin("user.role", "role")
.where("role.name = :roleName", { roleName: RoleEnum.USER })
.leftJoin("user.groups", "groups")
.addSelect(["groups.id", "groups.name"])
.leftJoinAndSelect("user.invoices", "invoices")
.leftJoinAndSelect("user.subscriptions", "subscriptions")
.leftJoinAndSelect("user.tickets", "tickets");
if (queryDto.q) {
queryBuilder
.orWhere("user.firstName ILIKE :search", { search: `%${queryDto.q}%` })
.orWhere("user.lastName ILIKE :search", { search: `%${queryDto.q}%` })
.orWhere("user.userName ILIKE :search", { search: `%${queryDto.q}%` });
}
const [customers, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
return { customers, count };
}
/************************************************************ */
async createCustomer(createDto: CreateCustomerDto) {
const { cityId, address, postalCode, economicCode, companyRegisteredName, registrationId, nationalId, number, ...userData } = createDto;
const queryRunner = this.userRepository.manager.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const existUser = await queryRunner.manager.findOneBy(User, { email: createDto.email });
if (existUser) throw new BadRequestException(UserMessage.EMAIL_EXIST);
const role = await queryRunner.manager.findOneBy(Role, { name: RoleEnum.USER });
if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND);
const hashedPassword = await this.passwordService.hashPassword(createDto.password);
const legalUserData = economicCode
? {
economicCode,
companyRegisteredName,
registrationId,
nationalId,
number,
address: {
address,
city: cityId
? {
id: +cityId,
}
: undefined,
postalCode,
},
}
: undefined;
const user = queryRunner.manager.create(User, {
...userData,
password: hashedPassword,
roles: [role],
legalUser: legalUserData,
});
await queryRunner.manager.save(user);
await this.userSettingsService.createUserSettings(user.id, queryRunner);
await this.walletsService.createUserWallet(user.id, queryRunner);
await queryRunner.commitTransaction();
return {
message: CommonMessage.CREATED,
user,
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
/************************************************************ */
async findOneCustomer(userId: string) {
const customer = await this.userRepository.findOne({
where: {
id: userId,
roles: {
name: RoleEnum.USER,
},
},
relations: ["tickets", "subscriptions", "invoices"],
});
return { customer };
}
/************************************************************ */
async getAdmins(queryDto: SearchAdminQueryDto) {
const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.userRepository.createQueryBuilder("user");
queryBuilder
.leftJoinAndSelect("user.roles", "role")
// .addSelect(["role.id", "role.name"])
.where("role.isAdmin = :isAdmin", { isAdmin: true })
.leftJoinAndSelect("user.groups", "groups")
.leftJoinAndSelect("role.permissions", "permissions");
// .where("permissions.name IN (:...adminPermissions)", {
// adminPermissions: [PermissionEnum.ADMINS],
// });
// .addSelect(["permissions.id", "permissions.name"]);
if (queryDto.q) {
queryBuilder.andWhere("user.firstName ILIKE :q OR user.lastName ILIKE :q OR user.userName ILIKE :q", {
q: `%${queryDto.q}%`,
});
}
if (queryDto.permission) {
queryBuilder.andWhere("permissions.name = :permName", { permName: queryDto.permission });
}
queryBuilder.take(limit).skip(skip).orderBy("user.createdAt", "DESC");
const [users, count] = await queryBuilder.getManyAndCount();
return {
users,
count,
paginate: true,
};
}
/************************************************************ */
async getPermissions() {
const permissions = await this.permissionsRepository.find({ select: { id: true, name: true } });
return {
permissions,
};
}
/************************************************************ */
async getRoles(queryDto: SearchRolesQueryDto) {
const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.rolesRepository.createQueryBuilder("role");
queryBuilder
.where("role.isAdmin = :isAdmin", { isAdmin: true })
// .leftJoinAndSelect("role.permissions", "permissions")
.loadRelationCountAndMap("role.userCount", "role.users");
if (queryDto.q) {
queryBuilder.andWhere("role.name ILIKE :q", { q: `%${queryDto.q}%` });
}
queryBuilder.take(limit).skip(skip).orderBy("role.createdAt", "DESC");
const [roles, count] = await queryBuilder.getManyAndCount();
return {
roles,
count,
paginate: true,
};
}
async createRealUserData(createDto: CreateRealUserDto, userId: string) {
const userExist = await this.userRepository.findOneBy({
id: userId,
});
if (!userExist) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
console.log(userExist);
const existingRealUser = await this.realUserRepository.findOne({
where: { user: { id: userExist.id } },
});
console.log(existingRealUser);
if (existingRealUser) throw new BadRequestException(RealUserMessage.REAL_DATA_EXIST);
const { city } = await this.addressService.getCity(+createDto.cityId);
const realUser = this.realUserRepository.create({
...createDto,
user: userExist,
address: {
address: createDto.address,
city,
postalCode: createDto.postalCode,
user: userExist,
},
});
await this.realUserRepository.save(realUser);
return {
message: CommonMessage.CREATED,
realUser,
};
}
// /************************************************************ */
async createLegalUserData(createDto: CreateLegalUserDto, userId: string) {
const userExist = await this.userRepository.findOneBy({
id: userId,
});
if (!userExist) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
const existingLegalUser = await this.legalUserRepository.findOne({
where: { user: { id: userExist.id } },
});
if (existingLegalUser) throw new BadRequestException(LegalUserMessage.LEGAL_DATA_EXIST);
const { city } = await this.addressService.getCity(+createDto.cityId);
const legalUser = this.legalUserRepository.create({
...createDto,
user: userExist,
address: {
address: createDto.address,
city,
postalCode: createDto.postalCode,
user: userExist,
},
});
await this.legalUserRepository.save(legalUser);
return {
message: CommonMessage.CREATED,
legalUser,
};
}
private async createEmailHashLink(userId: string) {
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
const timestamp = Date.now();
const payload = `${userId}:${timestamp}`;
const hash = createHmac("sha256", secret).update(payload).digest("hex");
const url = new URL(`${this.configService.getOrThrow<string>("SITE_URL")}/users/verify-email`);
url.searchParams.append("token", hash);
url.searchParams.append("userId", userId);
url.searchParams.append("ts", timestamp.toString());
return url.toString();
}
/************************************************************ */
}