chore: update user fix
This commit is contained in:
@@ -66,10 +66,9 @@ export const enum AuthMessage {
|
||||
}
|
||||
|
||||
export const enum UserMessage {
|
||||
USER_NOT_FOUND = "کاربری با این شماره وجود ندارد",
|
||||
USER_NOT_FOUND = "کاربری با این مشخصات یافت نشد",
|
||||
USER_EXISTS = "با این شماره قبلا ثبت نام شده است",
|
||||
USER_REGISTER_SUCCESS = "ثبت نام موفقیت آمیز بود",
|
||||
INVALID_USER = "کاربری با این مشخصات یافت نشد",
|
||||
EMAIL_ADDRESS_ALREADY_EXISTS = "ایمیل قبلا ثبت شده است",
|
||||
EMAIL_USER_NOT_FOUND = "ایمیل یافت نشد",
|
||||
EMAIL_USER_DELETED_SUCCESSFULLY = "ایمیل با موفقیت حذف شد",
|
||||
@@ -337,6 +336,7 @@ export const enum BusinessMessage {
|
||||
DOMAIN_NOT_FOUND = "دامنه یافت نشد",
|
||||
DOMAIN_VERIFICATION_TOKEN_REQUIRED = "توکن تایید دامنه مورد نیاز است",
|
||||
INSUFFICIENT_QUOTA = "حجم شما برای ساخت ایمیل کافی نمی باشد",
|
||||
QUOTA_DOWNGRADE_NOT_ALLOWED = "کاهش حجم ایمیل مجاز نیست، فقط میتوانید حجم را افزایش دهید",
|
||||
}
|
||||
|
||||
export const enum DomainMessage {
|
||||
@@ -424,10 +424,18 @@ export const enum RoleMessage {
|
||||
}
|
||||
|
||||
export const enum SignatureMessage {
|
||||
NAME_NOT_EMPTY = "نام علامت توضیحی نمیتواند خالی باشد",
|
||||
NAME_MAX_LENGTH = "NAME_MAX_LENGTH",
|
||||
NAME_STRING = "NAME_STRING",
|
||||
TEXT_CONTENT_NOT_EMPTY = "TEXT_CONTENT_NOT_EMPTY",
|
||||
TEXT_CONTENT_STRING = "TEXT_CONTENT_STRING",
|
||||
TEXT_CONTENT_MAX_LENGTH = "TEXT_CONTENT_MAX_LENGTH",
|
||||
NAME_NOT_EMPTY = "نام امضاء نمیتواند خالی باشد",
|
||||
NAME_MAX_LENGTH = "نام امضاء نمیتواند بیشتر از حداکثر طول مجاز باشد",
|
||||
NAME_STRING = "نام امضاء باید یک رشته باشد",
|
||||
TEXT_CONTENT_NOT_EMPTY = "محتوای متنی امضاء نمیتواند خالی باشد",
|
||||
TEXT_CONTENT_STRING = "محتوای متنی امضاء باید یک رشته باشد",
|
||||
TEXT_CONTENT_MAX_LENGTH = "محتوای متنی امضاء نمیتواند بیشتر از حداکثر طول مجاز باشد",
|
||||
IS_ACTIVE_NOT_EMPTY = "وضعیت فعال بودن نمیتواند خالی باشد",
|
||||
IS_ACTIVE_BOOLEAN = "وضعیت فعال بودن باید بولین باشد",
|
||||
IS_DEFAULT_NOT_EMPTY = "وضعیت پیش فرض نمیتواند خالی باشد",
|
||||
IS_DEFAULT_BOOLEAN = "وضعیت پیش فرض باید بولین باشد",
|
||||
CREATED = "امضاء با موفقیت ایجاد شد",
|
||||
NOT_FOUND = "امضاء یافت نشد",
|
||||
DELETED = "امضاء با موفقیت حذف شد",
|
||||
TOGGLE_ACTIVE = "وضعیت امضاء با موفقیت تغییر کرد",
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ async function bootstrap() {
|
||||
// Setup WebSocket adapter
|
||||
app.useWebSocketAdapter(new IoAdapter(app));
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }));
|
||||
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
||||
|
||||
app.useGlobalInterceptors(new ResponseInterceptor(), new PaginationInterceptor());
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
|
||||
import { MailServerService } from "./services/mail-server.service";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
|
||||
@AuthGuards()
|
||||
@Controller("mail-server")
|
||||
export class MailServerController {
|
||||
constructor(private readonly mailServerService: MailServerService) {}
|
||||
|
||||
@Get("info")
|
||||
@ApiOperation({ summary: "Get mail server info" })
|
||||
@ApiResponse({ status: 200, description: "Mail server info" })
|
||||
getMailServerInfo() {
|
||||
return this.mailServerService.getMailServerInfo();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HttpModule } from "@nestjs/axios";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { MailServerController } from "./mail-server.controller";
|
||||
import { MailServerService } from "./services/mail-server.service";
|
||||
import { WildDuckAddressesService } from "./services/wildduck-addresses.service";
|
||||
import { WildDuckApplicationPasswordsService } from "./services/wildduck-application-passwords.service";
|
||||
@@ -25,6 +26,7 @@ import { wildduckConfig } from "../../configs/wildduck.config";
|
||||
|
||||
@Module({
|
||||
imports: [HttpModule.registerAsync(wildduckConfig())],
|
||||
controllers: [MailServerController],
|
||||
providers: [
|
||||
WildDuckBaseService,
|
||||
MailServerService,
|
||||
|
||||
@@ -19,6 +19,8 @@ import { WildDuckSubmissionService } from "./wildduck-submission.service";
|
||||
import { WildDuckTwoFactorService } from "./wildduck-two-factor.service";
|
||||
import { WildDuckUsersService } from "./wildduck-users.service";
|
||||
import { WildDuckWebhooksService } from "./wildduck-webhooks.service";
|
||||
import { DANAK_IMAPS_ENCRYPTION, DANAK_IMAPS_PORT, DANAK_SMTPS_ENCRYPTION, DANAK_SMTPS_PORT, DANAK_SMTPS_SERVER } from "../../../common/constants";
|
||||
import { DANAK_IMAPS_SERVER } from "../../../common/constants";
|
||||
|
||||
@Injectable()
|
||||
export class MailServerService {
|
||||
@@ -45,6 +47,23 @@ export class MailServerService {
|
||||
private readonly twoFactorService: WildDuckTwoFactorService,
|
||||
) {}
|
||||
|
||||
getMailServerInfo() {
|
||||
return {
|
||||
setupInfo: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Expose individual services for direct access
|
||||
get users() {
|
||||
return this.usersService;
|
||||
|
||||
@@ -27,7 +27,7 @@ export class QuotaSyncService {
|
||||
/**
|
||||
* Sync quota usage every 2 hours
|
||||
*/
|
||||
@Cron(CronExpression.EVERY_2_HOURS)
|
||||
@Cron(CronExpression.EVERY_10_SECONDS)
|
||||
async syncAllUsersQuota(): Promise<void> {
|
||||
const em = this.em.fork();
|
||||
this.logger.log("Starting quota synchronization for all users");
|
||||
@@ -66,7 +66,7 @@ export class QuotaSyncService {
|
||||
/**
|
||||
* Sync business quota usage every 4 hours
|
||||
*/
|
||||
@Cron(CronExpression.EVERY_4_HOURS)
|
||||
@Cron(CronExpression.EVERY_10_SECONDS)
|
||||
async syncAllBusinessQuotas(): Promise<void> {
|
||||
const em = this.em.fork();
|
||||
this.logger.log("Starting business quota synchronization");
|
||||
|
||||
@@ -10,40 +10,29 @@ export class CreateEmailSignatureDto {
|
||||
@ApiProperty({ description: "Name for the email signature", example: "Business Signature", maxLength: 100 })
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNotEmpty({ message: SignatureMessage.TEXT_CONTENT_NOT_EMPTY })
|
||||
@IsString({ message: SignatureMessage.TEXT_CONTENT_STRING })
|
||||
@MaxLength(5000, { message: SignatureMessage.TEXT_CONTENT_MAX_LENGTH })
|
||||
@ApiPropertyOptional({
|
||||
@ApiProperty({
|
||||
description: "Plain text content of the signature",
|
||||
example: "John Doe\nSoftware Engineer\nCompany Inc.\njohn@company.com",
|
||||
maxLength: 5000,
|
||||
})
|
||||
textContent?: string;
|
||||
textContent: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// @MaxLength(10000)
|
||||
// @ApiPropertyOptional({
|
||||
// description: "HTML content of the signature",
|
||||
// example: "<div><strong>John Doe</strong><br>Software Engineer<br>Company Inc.<br><a href='mailto:john@company.com'>john@company.com</a></div>",
|
||||
// maxLength: 10000,
|
||||
// })
|
||||
// htmlContent?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(10000)
|
||||
@ApiPropertyOptional({
|
||||
description: "HTML content of the signature",
|
||||
example: "<div><strong>John Doe</strong><br>Software Engineer<br>Company Inc.<br><a href='mailto:john@company.com'>john@company.com</a></div>",
|
||||
maxLength: 10000,
|
||||
})
|
||||
htmlContent?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@ApiPropertyOptional({
|
||||
description: "Whether this signature is active",
|
||||
default: true,
|
||||
})
|
||||
@IsNotEmpty({ message: SignatureMessage.IS_ACTIVE_NOT_EMPTY })
|
||||
@IsBoolean({ message: SignatureMessage.IS_ACTIVE_BOOLEAN })
|
||||
@ApiPropertyOptional({ description: "Whether this signature is active", default: true })
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@ApiPropertyOptional({
|
||||
description: "Whether this is the default signature for the user",
|
||||
default: false,
|
||||
})
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, UseGuards } from "@nestjs/common";
|
||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from "@nestjs/common";
|
||||
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
|
||||
import { CreateEmailSignatureDto } from "./DTO/create-email-signature.dto";
|
||||
import { UpdateEmailSignatureDto } from "./DTO/update-email-signature.dto";
|
||||
import { EmailSignature } from "./entities/email-signature.entity";
|
||||
import { EmailSignaturesService } from "./services/email-signatures.service";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { UserDec } from "../../common/decorators/user.decorator";
|
||||
import { JwtAuthGuard } from "../auth/guards/auth.guard";
|
||||
import { User } from "../users/entities/user.entity";
|
||||
|
||||
@ApiTags("Email Signatures")
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller("email-signatures")
|
||||
@AuthGuards()
|
||||
export class EmailSignaturesController {
|
||||
constructor(private readonly emailSignaturesService: EmailSignaturesService) {}
|
||||
|
||||
@@ -20,37 +18,22 @@ export class EmailSignaturesController {
|
||||
@ApiOperation({ summary: "Create a new email signature" })
|
||||
@ApiResponse({ status: 201, description: "Email signature created successfully", type: EmailSignature })
|
||||
@ApiResponse({ status: 400, description: "Bad request" })
|
||||
async create(@UserDec() user: User, @Body() createEmailSignatureDto: CreateEmailSignatureDto): Promise<EmailSignature> {
|
||||
create(@UserDec() user: User, @Body() createEmailSignatureDto: CreateEmailSignatureDto) {
|
||||
return this.emailSignaturesService.create(user.id, createEmailSignatureDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get all email signatures for the user" })
|
||||
@ApiResponse({ status: 200, description: "Email signatures retrieved successfully", type: [EmailSignature] })
|
||||
async findAll(@UserDec() user: User): Promise<EmailSignature[]> {
|
||||
return this.emailSignaturesService.findAll(user.id);
|
||||
}
|
||||
|
||||
@Get("active")
|
||||
@ApiOperation({ summary: "Get all active email signatures for the user" })
|
||||
@ApiResponse({ status: 200, description: "Active email signatures retrieved successfully", type: [EmailSignature] })
|
||||
async findActive(@UserDec() user: User): Promise<EmailSignature[]> {
|
||||
findActive(@UserDec() user: User) {
|
||||
return this.emailSignaturesService.findActive(user.id);
|
||||
}
|
||||
|
||||
@Get("default")
|
||||
@ApiOperation({ summary: "Get the default email signature for the user" })
|
||||
@ApiResponse({ status: 200, description: "Default email signature retrieved successfully", type: EmailSignature })
|
||||
@ApiResponse({ status: 404, description: "No default signature found" })
|
||||
async findDefault(@UserDec() user: User): Promise<EmailSignature | null> {
|
||||
return this.emailSignaturesService.findDefault(user.id);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a specific email signature" })
|
||||
@ApiResponse({ status: 200, description: "Email signature retrieved successfully", type: EmailSignature })
|
||||
@ApiResponse({ status: 404, description: "Email signature not found" })
|
||||
async findOne(@Param("id") id: string, @UserDec() user: User): Promise<EmailSignature> {
|
||||
findOne(@Param("id") id: string, @UserDec() user: User) {
|
||||
return this.emailSignaturesService.findOne(id, user.id);
|
||||
}
|
||||
|
||||
@@ -58,40 +41,23 @@ export class EmailSignaturesController {
|
||||
@ApiOperation({ summary: "Update an email signature" })
|
||||
@ApiResponse({ status: 200, description: "Email signature updated successfully", type: EmailSignature })
|
||||
@ApiResponse({ status: 404, description: "Email signature not found" })
|
||||
async update(@Param("id") id: string, @UserDec() user: User, @Body() updateEmailSignatureDto: UpdateEmailSignatureDto): Promise<EmailSignature> {
|
||||
update(@Param("id") id: string, @UserDec() user: User, @Body() updateEmailSignatureDto: UpdateEmailSignatureDto) {
|
||||
return this.emailSignaturesService.update(id, user.id, updateEmailSignatureDto);
|
||||
}
|
||||
|
||||
@Put(":id/set-default")
|
||||
@ApiOperation({ summary: "Set an email signature as default" })
|
||||
@ApiResponse({ status: 200, description: "Email signature set as default successfully", type: EmailSignature })
|
||||
@ApiResponse({ status: 400, description: "Cannot set inactive signature as default" })
|
||||
@ApiResponse({ status: 404, description: "Email signature not found" })
|
||||
async setAsDefault(@Param("id") id: string, @UserDec() user: User): Promise<EmailSignature> {
|
||||
return this.emailSignaturesService.setAsDefault(id, user.id);
|
||||
}
|
||||
|
||||
@Put(":id/activate")
|
||||
@Put(":id/toggle-active")
|
||||
@ApiOperation({ summary: "Activate an email signature" })
|
||||
@ApiResponse({ status: 200, description: "Email signature activated successfully", type: EmailSignature })
|
||||
@ApiResponse({ status: 404, description: "Email signature not found" })
|
||||
async activate(@Param("id") id: string, @UserDec() user: User): Promise<EmailSignature> {
|
||||
return this.emailSignaturesService.activate(id, user.id);
|
||||
}
|
||||
|
||||
@Put(":id/deactivate")
|
||||
@ApiOperation({ summary: "Deactivate an email signature" })
|
||||
@ApiResponse({ status: 200, description: "Email signature deactivated successfully", type: EmailSignature })
|
||||
@ApiResponse({ status: 404, description: "Email signature not found" })
|
||||
async deactivate(@Param("id") id: string, @UserDec() user: User): Promise<EmailSignature> {
|
||||
return this.emailSignaturesService.deactivate(id, user.id);
|
||||
toggleActive(@Param("id") id: string, @UserDec() user: User) {
|
||||
return this.emailSignaturesService.toggleActive(id, user.id);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@ApiOperation({ summary: "Delete an email signature" })
|
||||
@ApiResponse({ status: 200, description: "Email signature deleted successfully" })
|
||||
@ApiResponse({ status: 404, description: "Email signature not found" })
|
||||
async remove(@Param("id") id: string, @UserDec() user: User): Promise<void> {
|
||||
remove(@Param("id") id: string, @UserDec() user: User) {
|
||||
return this.emailSignaturesService.remove(id, user.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,5 @@ export class EmailSignature extends BaseEntity {
|
||||
@Property({ type: "boolean", default: true })
|
||||
isActive!: boolean & Opt;
|
||||
|
||||
@Property({ type: "boolean", default: false })
|
||||
isDefault!: boolean & Opt;
|
||||
|
||||
[EntityRepositoryType]?: EmailSignatureRepository;
|
||||
}
|
||||
|
||||
@@ -4,25 +4,14 @@ import { EmailSignature } from "../entities/email-signature.entity";
|
||||
|
||||
export class EmailSignatureRepository extends EntityRepository<EmailSignature> {
|
||||
async findByUserId(userId: string): Promise<EmailSignature[]> {
|
||||
return this.find({ user: userId }, { orderBy: { isDefault: "DESC", createdAt: "DESC" } });
|
||||
return this.find({ user: userId }, { orderBy: { createdAt: "DESC" } });
|
||||
}
|
||||
|
||||
async findActiveByUserId(userId: string): Promise<EmailSignature[]> {
|
||||
return this.find({ user: userId, isActive: true }, { orderBy: { isDefault: "DESC", createdAt: "DESC" } });
|
||||
}
|
||||
|
||||
async findDefaultByUserId(userId: string): Promise<EmailSignature | null> {
|
||||
return this.findOne({ user: userId, isDefault: true, isActive: true });
|
||||
return this.find({ user: userId, isActive: true }, { orderBy: { createdAt: "DESC" } });
|
||||
}
|
||||
|
||||
async findByIdAndUserId(id: string, userId: string): Promise<EmailSignature | null> {
|
||||
return this.findOne({ id, user: userId });
|
||||
}
|
||||
|
||||
async updateDefaultSignature(userId: string, newDefaultId: string): Promise<void> {
|
||||
// remove default from all signatures for this user
|
||||
await this.nativeUpdate({ user: userId }, { isDefault: false });
|
||||
// set the new default
|
||||
await this.nativeUpdate({ id: newDefaultId, user: userId }, { isDefault: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EntityManager } from "@mikro-orm/core";
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
|
||||
import { SignatureMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { CreateEmailSignatureDto } from "../DTO/create-email-signature.dto";
|
||||
import { UpdateEmailSignatureDto } from "../DTO/update-email-signature.dto";
|
||||
import { EmailSignature } from "../entities/email-signature.entity";
|
||||
import { EmailSignatureRepository } from "../repositories/email-signature.repository";
|
||||
|
||||
@Injectable()
|
||||
@@ -13,94 +13,66 @@ export class EmailSignaturesService {
|
||||
private readonly emailSignatureRepository: EmailSignatureRepository,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
|
||||
async create(userId: string, createDto: CreateEmailSignatureDto): Promise<EmailSignature> {
|
||||
// Validate that at least one content type is provided
|
||||
if (!createDto.textContent && !createDto.htmlContent) {
|
||||
throw new BadRequestException("At least one content type (text or HTML) must be provided");
|
||||
}
|
||||
|
||||
if (createDto.isDefault) await this.emailSignatureRepository.updateDefaultSignature(userId, "");
|
||||
//#########################
|
||||
async create(userId: string, createDto: CreateEmailSignatureDto) {
|
||||
const user = await this.em.findOne(User, { id: userId, deletedAt: null });
|
||||
if (!user) throw new NotFoundException(UserMessage.USER_NOT_FOUND);
|
||||
|
||||
const signature = this.emailSignatureRepository.create({
|
||||
...createDto,
|
||||
user: { id: userId } as User,
|
||||
user,
|
||||
});
|
||||
|
||||
await this.em.persistAndFlush(signature);
|
||||
return signature;
|
||||
return {
|
||||
message: SignatureMessage.CREATED,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
//#########################
|
||||
|
||||
async findActive(userId: string) {
|
||||
const signatures = await this.emailSignatureRepository.findActiveByUserId(userId);
|
||||
return {
|
||||
signatures,
|
||||
};
|
||||
}
|
||||
|
||||
async findAll(userId: string): Promise<EmailSignature[]> {
|
||||
return this.emailSignatureRepository.findByUserId(userId);
|
||||
}
|
||||
//#########################
|
||||
|
||||
async findActive(userId: string): Promise<EmailSignature[]> {
|
||||
return this.emailSignatureRepository.findActiveByUserId(userId);
|
||||
}
|
||||
async update(id: string, userId: string, updateDto: UpdateEmailSignatureDto) {
|
||||
const { signature } = await this.findOne(id, userId);
|
||||
|
||||
async findDefault(userId: string): Promise<EmailSignature | null> {
|
||||
return this.emailSignatureRepository.findDefaultByUserId(userId);
|
||||
}
|
||||
|
||||
async findOne(id: string, userId: string): Promise<EmailSignature> {
|
||||
const signature = await this.emailSignatureRepository.findByIdAndUserId(id, userId);
|
||||
if (!signature) {
|
||||
throw new NotFoundException("Email signature not found");
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
async update(id: string, userId: string, updateDto: UpdateEmailSignatureDto): Promise<EmailSignature> {
|
||||
const signature = await this.findOne(id, userId);
|
||||
|
||||
// If updating to default, remove default from other signatures
|
||||
if (updateDto.isDefault && !signature.isDefault) {
|
||||
await this.emailSignatureRepository.updateDefaultSignature(userId, id);
|
||||
}
|
||||
|
||||
// Update the signature
|
||||
Object.assign(signature, updateDto);
|
||||
this.emailSignatureRepository.assign(signature, updateDto);
|
||||
await this.em.flush();
|
||||
|
||||
return signature;
|
||||
return { signature };
|
||||
}
|
||||
//#########################
|
||||
|
||||
async setAsDefault(id: string, userId: string): Promise<EmailSignature> {
|
||||
const signature = await this.findOne(id, userId);
|
||||
|
||||
if (!signature.isActive) {
|
||||
throw new BadRequestException("Cannot set inactive signature as default");
|
||||
}
|
||||
|
||||
await this.emailSignatureRepository.updateDefaultSignature(userId, id);
|
||||
signature.isDefault = true;
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
async remove(id: string, userId: string): Promise<void> {
|
||||
const signature = await this.findOne(id, userId);
|
||||
async remove(id: string, userId: string) {
|
||||
const { signature } = await this.findOne(id, userId);
|
||||
await this.em.removeAndFlush(signature);
|
||||
return { message: SignatureMessage.DELETED };
|
||||
}
|
||||
//#########################
|
||||
|
||||
async toggleActive(id: string, userId: string) {
|
||||
const { signature } = await this.findOne(id, userId);
|
||||
signature.isActive = !signature.isActive;
|
||||
|
||||
await this.em.flush();
|
||||
|
||||
return {
|
||||
message: SignatureMessage.TOGGLE_ACTIVE,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
async activate(id: string, userId: string): Promise<EmailSignature> {
|
||||
const signature = await this.findOne(id, userId);
|
||||
signature.isActive = true;
|
||||
await this.em.flush();
|
||||
return signature;
|
||||
}
|
||||
//#########################
|
||||
async findOne(id: string, userId: string) {
|
||||
const signature = await this.emailSignatureRepository.findByIdAndUserId(id, userId);
|
||||
if (!signature) throw new BadRequestException(SignatureMessage.NOT_FOUND);
|
||||
|
||||
async deactivate(id: string, userId: string): Promise<EmailSignature> {
|
||||
const signature = await this.findOne(id, userId);
|
||||
|
||||
// If this is the default signature, we need to handle it
|
||||
if (signature.isDefault) {
|
||||
signature.isDefault = false;
|
||||
}
|
||||
|
||||
signature.isActive = false;
|
||||
await this.em.flush();
|
||||
return signature;
|
||||
return { signature };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export class RefreshToken extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 255 })
|
||||
token!: string;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@ManyToOne(() => User, { deleteRule: "cascade" })
|
||||
user!: User;
|
||||
|
||||
@Property({ type: "timestamptz" })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, OneToOne, Opt, Property } from "@mikro-orm/core";
|
||||
import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, OneToOne, Opt, Property, Unique } from "@mikro-orm/core";
|
||||
|
||||
import { RefreshToken } from "./refresh-token.entity";
|
||||
import { Role } from "./role.entity";
|
||||
@@ -9,6 +9,7 @@ import { EmailSignature } from "../../signatures/entities/email-signature.entity
|
||||
import { UserRepository } from "../repositories/user.repository";
|
||||
|
||||
@Entity({ repository: () => UserRepository })
|
||||
@Unique({ properties: ["userName", "domain"], name: "unique_user_name_domain", options: { where: "deleted_at is null" } })
|
||||
export class User extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 150, nullable: false })
|
||||
title!: string;
|
||||
|
||||
@@ -2,14 +2,6 @@ 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 { QUOTA_CONSTANTS } from "../../businesses/constant";
|
||||
import { Business } from "../../businesses/entities/business.entity";
|
||||
@@ -106,6 +98,7 @@ export class UsersService {
|
||||
if (domain.status !== DomainStatus.VERIFIED) throw new BadRequestException(DomainMessage.NOT_VERIFIED);
|
||||
|
||||
const emailAddress = `${username}@${domain.name}`;
|
||||
const finalUserName = `${username}-${domain.name}`;
|
||||
|
||||
const existingUser = await this.userRepository.findOne({ emailAddress });
|
||||
if (existingUser) throw new BadRequestException(UserMessage.EMAIL_ADDRESS_ALREADY_EXISTS);
|
||||
@@ -120,7 +113,7 @@ export class UsersService {
|
||||
|
||||
const mailServerUser = await firstValueFrom(
|
||||
this.mailServerService.users.createUser({
|
||||
username: `${username}-${domain.name}`,
|
||||
username: finalUserName,
|
||||
password,
|
||||
address: emailAddress,
|
||||
name: displayName || username,
|
||||
@@ -132,7 +125,7 @@ export class UsersService {
|
||||
|
||||
const user = this.userRepository.create({
|
||||
title: title || displayName || username,
|
||||
userName: username,
|
||||
userName: finalUserName,
|
||||
password: hashedPassword,
|
||||
emailAddress,
|
||||
emailEnabled: true,
|
||||
@@ -182,19 +175,6 @@ export class UsersService {
|
||||
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) {
|
||||
@@ -245,14 +225,23 @@ export class UsersService {
|
||||
async updateEmailUser(userId: string, businessId: string, updateEmailUserDto: UpdateEmailUserDto) {
|
||||
const { password, displayName, quota, aliases, title } = updateEmailUserDto;
|
||||
|
||||
const user = await this.userRepository.findOne({
|
||||
id: userId,
|
||||
business: { id: businessId },
|
||||
emailEnabled: true,
|
||||
});
|
||||
const user = await this.userRepository.findOne(
|
||||
{
|
||||
id: userId,
|
||||
business: { id: businessId },
|
||||
emailEnabled: true,
|
||||
},
|
||||
{ populate: ["business"] },
|
||||
);
|
||||
|
||||
if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND);
|
||||
|
||||
// Validate business quota if quota is being updated
|
||||
if (quota) {
|
||||
const currentQuota = user.emailQuota || 0;
|
||||
this.validateBusinessQuotaAvailability(user.business, currentQuota, quota);
|
||||
}
|
||||
|
||||
try {
|
||||
const updateData: UpdateUserDto = {};
|
||||
|
||||
@@ -299,7 +288,13 @@ export class UsersService {
|
||||
|
||||
if (displayName) user.displayName = displayName;
|
||||
|
||||
if (quota) user.emailQuota = quota;
|
||||
if (quota) {
|
||||
const currentQuota = user.emailQuota || 0;
|
||||
user.emailQuota = quota;
|
||||
|
||||
// Update business quota tracking
|
||||
this.updateBusinessQuotaTracking(user.business, currentQuota, quota);
|
||||
}
|
||||
|
||||
if (title) user.title = title;
|
||||
|
||||
@@ -330,14 +325,21 @@ export class UsersService {
|
||||
* 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,
|
||||
});
|
||||
const user = await this.userRepository.findOne(
|
||||
{
|
||||
id: userId,
|
||||
business: { id: businessId },
|
||||
emailEnabled: true,
|
||||
},
|
||||
{ populate: ["business"] },
|
||||
);
|
||||
|
||||
if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND);
|
||||
|
||||
// Validate business quota before updating
|
||||
const currentQuota = user.emailQuota || 0;
|
||||
this.validateBusinessQuotaAvailability(user.business, currentQuota, newQuota);
|
||||
|
||||
try {
|
||||
if (user.wildduckUserId) {
|
||||
await firstValueFrom(
|
||||
@@ -348,7 +350,11 @@ export class UsersService {
|
||||
}
|
||||
|
||||
user.emailQuota = newQuota;
|
||||
await this.em.persistAndFlush(user);
|
||||
|
||||
// Update business quota tracking
|
||||
this.updateBusinessQuotaTracking(user.business, currentQuota, newQuota);
|
||||
|
||||
await this.em.persistAndFlush([user, user.business]);
|
||||
|
||||
this.logger.log(`Email user quota updated: ${user.emailAddress} to ${newQuota} bytes`);
|
||||
|
||||
@@ -373,4 +379,33 @@ export class UsersService {
|
||||
status: user.isActive,
|
||||
};
|
||||
}
|
||||
|
||||
/*******************************/
|
||||
private validateBusinessQuotaAvailability(business: Business, currentUserQuota: number, newUserQuota: number): void {
|
||||
// Prevent quota downgrade - users can only upgrade their quota
|
||||
if (newUserQuota < currentUserQuota) {
|
||||
throw new BadRequestException(BusinessMessage.QUOTA_DOWNGRADE_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
const quotaDifference = newUserQuota - currentUserQuota;
|
||||
|
||||
if (quotaDifference > 0) {
|
||||
const remainingQuota = Number(business.remainingQuota) || 0;
|
||||
|
||||
if (quotaDifference > remainingQuota) {
|
||||
throw new BadRequestException(BusinessMessage.INSUFFICIENT_QUOTA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=====================================================
|
||||
|
||||
private updateBusinessQuotaTracking(business: Business, currentUserQuota: number, newUserQuota: number): void {
|
||||
const quotaDifference = newUserQuota - currentUserQuota;
|
||||
|
||||
business.usedQuota = Number(business.usedQuota) + quotaDifference;
|
||||
business.remainingQuota = Number(business.quota) - Number(business.usedQuota);
|
||||
|
||||
this.logger.log(`Updated business quota for ${business.name}: used=${business.usedQuota}, remaining=${business.remainingQuota}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user