Files
dmail-api/src/modules/users/services/users.service.ts
T
2025-07-09 16:18:54 +03:30

258 lines
9.0 KiB
TypeScript

import { EntityManager } 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 { UserListQueryDto } from "../DTO/user-list-query.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 getUserEmailIdFromId(userId: string) {
const user = await this.userRepository.findOne({ id: userId }, { populate: ["role"] });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
return user.wildduckUserId;
}
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,
wildduckUserId: 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!,
wildduckUserId: user.wildduckUserId!,
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, queryDto: UserListQueryDto) {
const [users, count] = await this.userRepository.getUserListForAdmin(businessId, queryDto);
return { users, count, paginate: true };
}
/*******************************/
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.wildduckUserId) {
await firstValueFrom(this.mailServerService.users.deleteUser(user.wildduckUserId));
}
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.wildduckUserId) {
await firstValueFrom(
this.mailServerService.users.updateUser(user.wildduckUserId, {
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,
};
}
}