262 lines
8.9 KiB
TypeScript
262 lines
8.9 KiB
TypeScript
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<User> = {
|
|
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,
|
|
};
|
|
}
|
|
}
|