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