chore: add new route

This commit is contained in:
mahyargdz
2025-07-20 10:15:13 +03:30
parent 066d6c8598
commit 5322b6b6c4
48 changed files with 1282 additions and 295 deletions
+58 -56
View File
@@ -11,6 +11,7 @@ import { DomainsService } from "../../domains/services/domains.service";
import { UpdateUserDto } from "../../mail-server/DTO";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { QuotaSyncService } from "../../quota-sync/services/quota-sync.service";
import { UserSettingsService } from "../../settings/services/user-settings.service";
import { Template } from "../../templates/entities/template.entity";
import { PasswordService } from "../../utils/services/password.service";
import { CreateEmailUserDto } from "../DTO/create-email-user.dto";
@@ -33,6 +34,7 @@ export class UsersService {
private readonly domainsService: DomainsService,
private readonly domainAutomationService: DomainAutomationService,
private readonly quotaSyncService: QuotaSyncService,
private readonly userSettingsService: UserSettingsService,
) {}
async getUserEmailIdFromId(userId: string) {
@@ -57,21 +59,24 @@ export class UsersService {
const user = await this.userRepository.findOne({ id: userId }, { exclude: ["password"], populate: ["role"] });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
const totalQuota = Number(user.emailQuota);
const usedQuota = Number(user.emailQuotaUsed);
const remainingQuota = totalQuota - usedQuota;
return {
user,
quota: {
total: Number(user.emailQuota),
used: Number(user.emailQuotaUsed),
remaining: Number(user.emailQuota - user.emailQuotaUsed),
totalInMB: Math.round(Number(user.emailQuota) / (1024 * 1024)),
totalInGB: Math.round(Number(user.emailQuota) / (1024 * 1024 * 1024)),
usedInMB: Math.round(Number(user.emailQuotaUsed) / (1024 * 1024)),
usedInGB: Math.round(Number(user.emailQuotaUsed) / (1024 * 1024 * 1024)),
remainingInMB: Math.round(Number(user.emailQuota - user.emailQuotaUsed) / (1024 * 1024)),
remainingInGB: Math.round(Number(user.emailQuota - user.emailQuotaUsed) / (1024 * 1024 * 1024)),
usagePercentage: Math.round((Number(user.emailQuotaUsed) / Number(user.emailQuota)) * 100),
total: totalQuota,
used: usedQuota,
remaining: remainingQuota,
totalInMB: Math.round(totalQuota / (1024 * 1024)),
totalInGB: Math.round(totalQuota / (1024 * 1024 * 1024)),
usedInMB: Math.round(usedQuota / (1024 * 1024)),
usedInGB: Math.round(usedQuota / (1024 * 1024 * 1024)),
remainingInMB: Math.round(remainingQuota / (1024 * 1024)),
remainingInGB: Math.round(remainingQuota / (1024 * 1024 * 1024)),
usagePercentage: totalQuota > 0 ? Math.round((usedQuota / totalQuota) * 100) : 0,
},
forwarders: user.forwarders || [],
};
}
/*******************************/
@@ -84,36 +89,39 @@ export class UsersService {
/*******************************/
async createEmailUser(createEmailUserDto: CreateEmailUserDto, businessId: string) {
const { username, password, domainId, displayName, quota, aliases, title, templateId, forwarders } = createEmailUserDto;
const business = await this.em.findOne(Business, { id: businessId });
if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND);
// Check if business has enough quota
const remainingQuota = Number(business.remainingQuota || 0);
if (remainingQuota < QUOTA_CONSTANTS.USER_QUOTA_DEDUCTION) {
throw new BadRequestException("Insufficient quota to create user. Please upgrade your plan.");
}
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 finalUserName = `${username}-${domain.name}`;
const finalDisplayName = `${username}`;
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);
const em = this.em.fork();
try {
em.begin();
const { username, password, domainId, displayName, quota, aliases, title, templateId, forwarders } = createEmailUserDto;
const business = await em.findOne(Business, { id: businessId });
if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND);
// Check if business has enough quota
const remainingQuota = Number(business.remainingQuota || 0);
if (remainingQuota < QUOTA_CONSTANTS.USER_QUOTA_DEDUCTION) {
throw new BadRequestException("Insufficient quota to create user. Please upgrade your plan.");
}
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 finalUserName = `${username}-${domain.name}`;
const finalDisplayName = `${username}`;
const existingUser = await em.findOne(User, { emailAddress });
if (existingUser) throw new BadRequestException(UserMessage.EMAIL_ADDRESS_ALREADY_EXISTS);
const hashedPassword = await this.passwordService.hashPassword(password);
const userRole = await em.findOne(Role, { name: RoleEnum.USER });
if (!userRole) throw new BadRequestException(RoleMessage.NOT_FOUND);
const userQuota = quota || QUOTA_CONSTANTS.DEFAULT_EMAIL_QUOTA_BYTES;
const mailServerUser = await firstValueFrom(
@@ -129,7 +137,7 @@ export class UsersService {
if (!mailServerUser.success) throw new BadRequestException(MailServerMessage.FAILED_TO_CREATE_ACCOUNT);
const user = this.userRepository.create({
const user = em.create(User, {
title: title || displayName || username,
userName: finalUserName,
password: hashedPassword,
@@ -145,14 +153,15 @@ export class UsersService {
forwarders: forwarders || [],
});
// Update business quota after successful user creation
await this.userSettingsService.createUserSettings(user.id, em);
business.usedQuota = Number(business.usedQuota) + QUOTA_CONSTANTS.USER_QUOTA_DEDUCTION;
business.remainingQuota = Number(business.quota) - Number(business.usedQuota);
await this.em.persistAndFlush([user, business]);
await em.persistAndFlush([user, business]);
if (templateId) {
const template = await this.em.findOne(Template, { id: templateId, business: { id: businessId } });
const template = await em.findOne(Template, { id: templateId, business: { id: businessId } });
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
user.template = template;
}
@@ -172,34 +181,27 @@ export class UsersService {
}
}
await this.em.flush();
await em.flush();
//sync quota
await this.quotaSyncService.scheduleUserQuotaSync(user.id, businessId, mailServerUser.id);
//sync business quota
await this.quotaSyncService.scheduleBusinessQuotaSync(businessId, business.name);
this.logger.log(`Email user created: ${emailAddress} for business ${businessId}`);
await this.domainAutomationService.createDefaultMailboxes(mailServerUser.id);
await em.commit();
return {
message: UserMessage.EMAIL_USER_CREATED_SUCCESSFULLY,
user: {
id: user.id,
emailAddress: user.emailAddress!,
displayName: user.displayName!,
wildduckUserId: user.wildduckUserId!,
emailQuota: user.emailQuota!,
emailEnabled: user.emailEnabled,
domain: domain.name,
forwarders: user.forwarders || [],
createdAt: user.createdAt,
isActive: user.isActive,
},
message: UserMessage.EMAIL_USER_CREATED_SUCCESSFULLY,
};
} catch (error) {
this.logger.error(`Failed to create email user ${emailAddress}:`, error);
await em.rollback();
this.logger.error(`Failed to create email user ${createEmailUserDto.username}@${createEmailUserDto.domainId}:`, error);
throw new BadRequestException(MailServerMessage.FAILED_TO_CREATE_ACCOUNT);
}
}