import { EntityManager, FilterQuery } from "@mikro-orm/postgresql"; import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common"; import { firstValueFrom } from "rxjs"; import { DANAK_IMAPS_ENCRYPTION, DANAK_IMAPS_PORT, DANAK_IMAPS_SERVER, DANAK_SMTPS_ENCRYPTION, DANAK_SMTPS_PORT, DANAK_SMTPS_SERVER, } from "../../../common/constants"; import { BusinessMessage, DomainMessage, MailServerMessage, RoleMessage, UserMessage } from "../../../common/enums/message.enum"; import { Business } from "../../businesses/entities/business.entity"; import { DomainStatus } from "../../domains/enums/domain-status.enum"; import { DomainsService } from "../../domains/services/domains.service"; import { MailServerService } from "../../mail-server/services/mail-server.service"; import { PasswordService } from "../../utils/services/password.service"; import { CreateEmailUserDto } from "../DTO/create-email-user.dto"; import { Role } from "../entities/role.entity"; import { User } from "../entities/user.entity"; import { RoleEnum } from "../enums/role.enum"; import { UserRepository } from "../repositories/user.repository"; @Injectable() export class UsersService { private readonly logger = new Logger(UsersService.name); constructor( private readonly userRepository: UserRepository, private readonly em: EntityManager, private readonly passwordService: PasswordService, private readonly mailServerService: MailServerService, private readonly domainsService: DomainsService, ) {} async findOneWithEmail(email: string, businessId: string) { const user = await this.userRepository.findOne({ emailAddress: email, business: { id: businessId } }, { populate: ["role"] }); return user; } async findOneById(id: string) { const user = await this.userRepository.findOne({ id }, { populate: ["role"] }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return { user }; } /*******************************/ async getMe(userId: string) { const user = await this.userRepository.findOne({ id: userId }, { populate: ["role"] }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return { user }; } /*******************************/ async findOneByIdWithEntityManager(id: string, em: EntityManager) { const user = await em.findOne(User, { id }, { populate: ["role"] }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); return user; } /*******************************/ async createEmailUser(createEmailUserDto: CreateEmailUserDto, businessId: string) { const { username, password, domainId, displayName, quota, aliases, title } = createEmailUserDto; const business = await this.em.findOne(Business, { id: businessId }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); const domain = await this.domainsService.getDomainById(domainId, businessId); if (domain.business.id !== businessId) throw new BadRequestException(DomainMessage.NOT_BELONG_TO_BUSINESS); if (domain.status !== DomainStatus.VERIFIED) throw new BadRequestException(DomainMessage.NOT_VERIFIED); const emailAddress = `${username}@${domain.name}`; const existingUser = await this.userRepository.findOne({ emailAddress }); if (existingUser) throw new BadRequestException(UserMessage.EMAIL_ADDRESS_ALREADY_EXISTS); const hashedPassword = await this.passwordService.hashPassword(password); const userRole = await this.em.findOne(Role, { name: RoleEnum.USER }); if (!userRole) throw new BadRequestException(RoleMessage.NOT_FOUND); try { const mailServerUser = await firstValueFrom( this.mailServerService.users.createUser({ username, password, address: emailAddress, name: displayName || username, quota: quota || 1073741824, language: "fa", }), ); if (!mailServerUser.success) throw new BadRequestException(MailServerMessage.FAILED_TO_CREATE_ACCOUNT); const user = this.userRepository.create({ title: title || displayName || username, userName: username, password: hashedPassword, emailAddress, emailEnabled: true, emailQuota: quota || 1073741824, emailAccountId: mailServerUser.id, displayName: displayName || username, isActive: true, business, domain, role: userRole, }); await this.em.persistAndFlush(user); if (aliases && aliases.length > 0) { for (const alias of aliases) { try { await firstValueFrom( this.mailServerService.addresses.createUserAddress(mailServerUser.id, { address: alias, main: false, }), ); } catch (error) { this.logger.warn(`Failed to create alias ${alias} for user ${emailAddress}:`, error); } } } this.logger.log(`Email user created: ${emailAddress} for business ${businessId}`); return { user: { id: user.id, emailAddress: user.emailAddress!, displayName: user.displayName!, emailAccountId: user.emailAccountId!, emailQuota: user.emailQuota!, emailEnabled: user.emailEnabled, domain: domain.name, createdAt: user.createdAt, isActive: user.isActive, }, setupInfo: { emailAddress: user.emailAddress!, imapSettings: { server: DANAK_IMAPS_SERVER, port: DANAK_IMAPS_PORT, encryption: DANAK_IMAPS_ENCRYPTION, }, smtpSettings: { server: DANAK_SMTPS_SERVER, port: DANAK_SMTPS_PORT, encryption: DANAK_SMTPS_ENCRYPTION, }, }, message: UserMessage.EMAIL_USER_CREATED_SUCCESSFULLY, }; } catch (error) { this.logger.error(`Failed to create email user ${emailAddress}:`, error); throw new BadRequestException(MailServerMessage.FAILED_TO_CREATE_ACCOUNT); } } /*******************************/ async getEmailUsers(businessId: string, domainId?: string) { const whereClause: FilterQuery = { business: { id: businessId }, emailEnabled: true, deletedAt: null, }; if (domainId) whereClause.domain = { id: domainId }; const users = await this.userRepository.find(whereClause, { populate: ["domain", "business"], orderBy: { createdAt: "DESC" }, }); return { users }; } /*******************************/ async deleteEmailUser(userId: string, businessId: string) { const user = await this.userRepository.findOne({ id: userId, business: { id: businessId }, emailEnabled: true, }); if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND); try { if (user.emailAccountId) { await firstValueFrom(this.mailServerService.users.deleteUser(user.emailAccountId)); } user.deletedAt = new Date(); user.isActive = false; user.emailEnabled = false; await this.em.persistAndFlush(user); this.logger.log(`Email user deleted: ${user.emailAddress} for business ${businessId}`); return { message: UserMessage.EMAIL_USER_DELETED_SUCCESSFULLY }; } catch (error) { this.logger.error(`Failed to delete email user ${user.emailAddress}:`, error); throw new BadRequestException(UserMessage.FAILED_TO_DELETE_EMAIL_USER); } } /** * Update email user quota */ async updateEmailUserQuota(userId: string, businessId: string, newQuota: number) { const user = await this.userRepository.findOne({ id: userId, business: { id: businessId }, emailEnabled: true, }); if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND); try { if (user.emailAccountId) { await firstValueFrom( this.mailServerService.users.updateUser(user.emailAccountId, { quota: newQuota, }), ); } user.emailQuota = newQuota; await this.em.persistAndFlush(user); this.logger.log(`Email user quota updated: ${user.emailAddress} to ${newQuota} bytes`); return { message: UserMessage.EMAIL_USER_QUOTA_UPDATED_SUCCESSFULLY, user }; } catch (error) { this.logger.error(`Failed to update quota for email user ${user.emailAddress}:`, error); throw new BadRequestException(UserMessage.FAILED_TO_UPDATE_EMAIL_USER_QUOTA); } } //===================================================== async updateEmailUserStatus(userId: string, businessId: string) { const user = await this.userRepository.findOne({ id: userId, business: { id: businessId }, emailEnabled: true }); if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND); user.isActive = !user.isActive; await this.em.flush(); return { message: UserMessage.STATUS_UPDATED, status: user.isActive, }; } }