chore: add update users

This commit is contained in:
mahyargdz
2025-07-13 09:45:18 +03:30
parent b01d77e36e
commit ed0e14c89a
6 changed files with 154 additions and 56 deletions
+1
View File
@@ -99,6 +99,7 @@ export const enum UserMessage {
TITLE_OPTIONAL = "عنوان اختیاری است",
TITLE_NOT_EMPTY = "عنوان نمیتواند خالی باشد",
EMAIL_USER_CREATED_SUCCESSFULLY = "ایمیل با موفقیت ایجاد شد",
EMAIL_USER_UPDATED_SUCCESSFULLY = "ایمیل کاربر با موفقیت به‌روزرسانی شد",
STATUS_UPDATED = "وضعیت کاربر با موفقیت تغییر کرد",
}
+1 -1
View File
@@ -213,7 +213,7 @@ export class EmailController {
@Post("messages/bulk-action")
@ApiOperation({
summary: "Perform bulk actions on multiple messages",
description: "Apply actions (seen, delete, archive, favorite, junk, trash) to multiple messages at once",
description: "Apply actions (seen, delete, archive, unarchive, favorite, unfavorite, junk, trash, restore) to multiple messages at once",
})
@ApiResponse({ status: 200, description: "Bulk action completed successfully" })
@ApiResponse({ status: 206, description: "Bulk action partially completed" })
+15 -55
View File
@@ -2,18 +2,12 @@ import { ApiPropertyOptional } from "@nestjs/swagger";
import { IsArray, IsBoolean, IsNumber, IsOptional, IsString, IsUrl } from "class-validator";
export class UpdateUserDto {
@ApiPropertyOptional({
description: "Updated display name for the user",
example: "Updated User Name",
})
@ApiPropertyOptional({ description: "Updated display name for the user", example: "Updated User Name" })
@IsOptional()
@IsString()
name?: string;
@ApiPropertyOptional({
description: "Updated password for the user",
example: "newsecretpassword123",
})
@ApiPropertyOptional({ description: "Updated password for the user", example: "newsecretpassword123" })
@IsOptional()
@IsString()
password?: string;
@@ -27,102 +21,68 @@ export class UpdateUserDto {
@IsString({ each: true })
forward?: string[];
@ApiPropertyOptional({
description: "URL to upload all messages to",
example: "https://example.com/webhook",
})
@ApiPropertyOptional({ description: "URL to upload all messages to", example: "https://example.com/webhook" })
@IsOptional()
@IsUrl()
targetUrl?: string;
@ApiPropertyOptional({
description: "Maximum storage in bytes allowed for this user",
example: 2147483648,
})
@ApiPropertyOptional({ description: "Maximum storage in bytes allowed for this user", example: 2147483648 })
@IsOptional()
@IsNumber()
quota?: number;
@ApiPropertyOptional({
description: "Default retention time in milliseconds for mailboxes",
example: 2592000000,
})
@ApiPropertyOptional({ description: "Default retention time in milliseconds for mailboxes", example: 2592000000 })
@IsOptional()
@IsNumber()
retention?: number;
@ApiPropertyOptional({
description: "Language code for the user",
example: "en",
})
@ApiPropertyOptional({ description: "Language code for the user", example: "en" })
@IsOptional()
@IsString()
language?: string;
@ApiPropertyOptional({
description: "Maximum number of recipients allowed to send mail to in 24h",
example: 3000,
})
@ApiPropertyOptional({ description: "Maximum number of recipients allowed to send mail to in 24h", example: 3000 })
@IsOptional()
@IsNumber()
recipients?: number;
@ApiPropertyOptional({
description: "Maximum number of forwarded emails in 24h",
example: 3000,
})
@ApiPropertyOptional({ description: "Maximum number of forwarded emails in 24h", example: 3000 })
@IsOptional()
@IsNumber()
forwards?: number;
@ApiPropertyOptional({
description: "Array of tags to associate with the user",
example: ["premium", "customer"],
})
@ApiPropertyOptional({ description: "Array of tags to associate with the user", example: ["premium", "customer"] })
@IsOptional()
@IsArray()
@IsString({ each: true })
tags?: string[];
@ApiPropertyOptional({
description: "PGP public key for encryption",
})
@ApiPropertyOptional({ description: "PGP public key for encryption" })
@IsOptional()
@IsString()
pubKey?: string;
@ApiPropertyOptional({
description: "Whether to encrypt stored messages",
})
@ApiPropertyOptional({ description: "Whether to encrypt stored messages" })
@IsOptional()
@IsBoolean()
encryptMessages?: boolean;
@ApiPropertyOptional({
description: "Whether to encrypt forwarded messages",
})
@ApiPropertyOptional({ description: "Whether to encrypt forwarded messages" })
@IsOptional()
@IsBoolean()
encryptForwarded?: boolean;
@ApiPropertyOptional({
description: "Current password for verification",
})
@ApiPropertyOptional({ description: "Current password for verification" })
@IsOptional()
@IsString()
existingPassword?: string;
@ApiPropertyOptional({
description: "Session identifier for logging",
})
@ApiPropertyOptional({ description: "Session identifier for logging" })
@IsOptional()
@IsString()
sess?: string;
@ApiPropertyOptional({
description: "IP address the request was made from",
example: "192.168.1.1",
})
@ApiPropertyOptional({ description: "IP address the request was made from", example: "192.168.1.1" })
@IsOptional()
@IsString()
ip?: string;
@@ -0,0 +1,41 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { Transform } from "class-transformer";
import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString, Matches, MinLength } from "class-validator";
import { UserMessage } from "../../../common/enums/message.enum";
export class UpdateEmailUserDto {
@IsOptional()
@IsString({ message: UserMessage.PASSWORD_STRING })
@MinLength(8, { message: UserMessage.PASSWORD_MIN_LENGTH })
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, { message: UserMessage.PASSWORD_MATCHES })
@ApiPropertyOptional({ description: "New password for email account", example: "NewSecurePassword123!", minLength: 8 })
password?: string;
@IsOptional()
@IsString({ message: UserMessage.DISPLAY_NAME_STRING })
@Transform(({ value }) => value?.trim())
@ApiPropertyOptional({ description: "Display name for the user", example: "John Doe" })
displayName?: string;
@IsOptional()
@IsNotEmpty({ message: UserMessage.QUOTA_NOT_EMPTY })
@IsNumber({ allowNaN: false, allowInfinity: false }, { message: UserMessage.QUOTA_NUMBER })
@ApiPropertyOptional({ description: "Email quota in bytes", example: 2147483648 })
quota?: number;
@IsOptional()
@IsArray({ message: UserMessage.ALIASES_ARRAY })
@IsString({ each: true, message: UserMessage.ALIASES_STRING })
@ApiPropertyOptional({
description: "Email aliases for this user (will replace existing aliases)",
example: ["johndoe@example.com", "j.doe@example.com"],
})
aliases?: string[];
@IsOptional()
@IsString({ message: UserMessage.TITLE_STRING })
@Transform(({ value }) => value?.trim())
@ApiPropertyOptional({ description: "User role/title", example: "Senior Marketing Manager" })
title?: string;
}
@@ -16,9 +16,11 @@ import { Business } from "../../businesses/entities/business.entity";
import { DomainStatus } from "../../domains/enums/domain-status.enum";
import { DomainAutomationService } from "../../domains/services/domain-automation.service";
import { DomainsService } from "../../domains/services/domains.service";
import { UpdateUserDto } from "../../mail-server/DTO";
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 { UpdateEmailUserDto } from "../DTO/update-email-user.dto";
import { UserListQueryDto } from "../DTO/user-list-query.dto";
import { Role } from "../entities/role.entity";
import { User } from "../entities/user.entity";
@@ -238,6 +240,92 @@ 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,
});
if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND);
try {
const updateData: UpdateUserDto = {};
if (password) {
updateData.password = password;
const hashedPassword = await this.passwordService.hashPassword(password);
user.password = hashedPassword;
}
if (displayName) updateData.name = displayName;
if (quota) updateData.quota = quota;
if (Object.keys(updateData).length > 0) {
await firstValueFrom(this.mailServerService.users.updateUser(user.wildduckUserId!, updateData));
}
if (aliases && aliases.length > 0) {
const currentAliases = await firstValueFrom(this.mailServerService.addresses.listUserAddresses(user.wildduckUserId!));
for (const alias of currentAliases.results) {
if (!alias.main && alias.address !== user.emailAddress) {
try {
await firstValueFrom(this.mailServerService.addresses.deleteUserAddress(user.wildduckUserId!, alias.id));
} catch (error) {
this.logger.warn(`Failed to delete alias ${alias.address}:`, error);
}
}
}
for (const alias of aliases) {
try {
await firstValueFrom(
this.mailServerService.addresses.createUserAddress(user.wildduckUserId!, {
address: alias,
main: false,
}),
);
} catch (error) {
this.logger.warn(`Failed to create alias ${alias}:`, error);
}
}
}
if (displayName) user.displayName = displayName;
if (quota) user.emailQuota = quota;
if (title) user.title = title;
await this.em.flush();
this.logger.log(`Email user updated: ${user.emailAddress} for business ${businessId}`);
return {
message: UserMessage.EMAIL_USER_UPDATED_SUCCESSFULLY,
user: {
id: user.id,
emailAddress: user.emailAddress!,
displayName: user.displayName!,
title: user.title,
emailQuota: user.emailQuota!,
emailEnabled: user.emailEnabled,
isActive: user.isActive,
updatedAt: user.updatedAt,
},
};
} catch (error) {
this.logger.error(`Failed to update email user ${user.emailAddress}:`, error);
throw new BadRequestException("Failed to update email user");
}
}
/**
* Update email user quota
*/
+8
View File
@@ -2,6 +2,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseIntercepto
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
import { CreateEmailUserDto } from "./DTO/create-email-user.dto";
import { UpdateEmailUserDto } from "./DTO/update-email-user.dto";
import { UserListQueryDto } from "./DTO/user-list-query.dto";
import { UsersService } from "./services/users.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
@@ -45,6 +46,13 @@ export class UsersController {
return this.usersService.deleteEmailUser(paramDto.id, businessId);
}
@Patch(":id")
@ApiOperation({ summary: "Update email user" })
@ApiResponse({ status: 200, description: "Email user updated successfully" })
updateEmailUser(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string, @Body() body: UpdateEmailUserDto) {
return this.usersService.updateEmailUser(paramDto.id, businessId, body);
}
@Patch(":id/quota")
@ApiOperation({ summary: "Update email user quota" })
@ApiResponse({ status: 200, description: "Email user quota updated successfully" })