chore: change the email creation dto
This commit is contained in:
+15
-15
@@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/static": "^8.2.0",
|
"@fastify/static": "^8.2.0",
|
||||||
"@keyv/redis": "^4.4.0",
|
"@keyv/redis": "^4.5.0",
|
||||||
"@mikro-orm/cli": "^6.4.16",
|
"@mikro-orm/cli": "^6.4.16",
|
||||||
"@mikro-orm/core": "^6.4.16",
|
"@mikro-orm/core": "^6.4.16",
|
||||||
"@mikro-orm/nestjs": "^6.1.1",
|
"@mikro-orm/nestjs": "^6.1.1",
|
||||||
@@ -54,14 +54,14 @@
|
|||||||
"@nestjs/throttler": "^6.4.0",
|
"@nestjs/throttler": "^6.4.0",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"bullmq": "^5.54.1",
|
"bullmq": "^5.56.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"decimal.js": "^10.5.0",
|
"decimal.js": "^10.5.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.6.1",
|
||||||
"fastify": "^5.4.0",
|
"fastify": "^5.4.0",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.4",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
@@ -72,30 +72,30 @@
|
|||||||
"@commitlint/cli": "^19.8.1",
|
"@commitlint/cli": "^19.8.1",
|
||||||
"@commitlint/config-conventional": "^19.8.1",
|
"@commitlint/config-conventional": "^19.8.1",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.30.1",
|
||||||
"@nestjs/cli": "^11.0.7",
|
"@nestjs/cli": "^11.0.7",
|
||||||
"@nestjs/schematics": "^11.0.5",
|
"@nestjs/schematics": "^11.0.5",
|
||||||
"@nestjs/testing": "^11.1.3",
|
"@nestjs/testing": "^11.1.3",
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.6.0",
|
||||||
"@swc/core": "^1.12.1",
|
"@swc/core": "^1.12.9",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.15.32",
|
"@types/node": "^22.16.0",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/supertest": "^6.0.3",
|
"@types/supertest": "^6.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
"@typescript-eslint/eslint-plugin": "^8.35.1",
|
||||||
"@typescript-eslint/parser": "^8.34.1",
|
"@typescript-eslint/parser": "^8.35.1",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.30.1",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-import-resolver-typescript": "^3.10.1",
|
"eslint-import-resolver-typescript": "^3.10.1",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-prettier": "^5.5.0",
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
"globals": "^16.2.0",
|
"globals": "^16.3.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"lint-staged": "^15.5.2",
|
"lint-staged": "^15.5.2",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.6.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.1.1",
|
"supertest": "^7.1.1",
|
||||||
"ts-jest": "^29.4.0",
|
"ts-jest": "^29.4.0",
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.34.1"
|
"typescript-eslint": "^8.35.1"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
|
|||||||
Generated
+778
-780
File diff suppressed because it is too large
Load Diff
@@ -75,6 +75,30 @@ export const enum UserMessage {
|
|||||||
FAILED_TO_DELETE_EMAIL_USER = "خطا در حذف ایمیل",
|
FAILED_TO_DELETE_EMAIL_USER = "خطا در حذف ایمیل",
|
||||||
EMAIL_USER_QUOTA_UPDATED_SUCCESSFULLY = "ایمیل با موفقیت به روز رسانی شد",
|
EMAIL_USER_QUOTA_UPDATED_SUCCESSFULLY = "ایمیل با موفقیت به روز رسانی شد",
|
||||||
FAILED_TO_UPDATE_EMAIL_USER_QUOTA = "خطا در به روز رسانی حجم ایمیل",
|
FAILED_TO_UPDATE_EMAIL_USER_QUOTA = "خطا در به روز رسانی حجم ایمیل",
|
||||||
|
USERNAME_REQUIRED = "نام کاربری الزامی است",
|
||||||
|
USERNAME_STRING = "نام کاربری باید یک رشته باشد",
|
||||||
|
USERNAME_MIN_LENGTH = "نام کاربری باید حداقل ۲ کاراکتر باشد",
|
||||||
|
USERNAME_MATCHES = "نام کاربری فقط میتواند شامل حروف، اعداد، نقطه، خط زیر و خط فاصله باشد",
|
||||||
|
PASSWORD_MATCHES = "رمز عبور باید حداقل ۸ کاراکتر باشد و شامل حروف بزرگ، حروف کوچک، اعداد و علائم خاص باشد",
|
||||||
|
PASSWORD_STRING = "رمز عبور باید یک رشته باشد",
|
||||||
|
PASSWORD_REQUIRED = "رمز عبور الزامی است",
|
||||||
|
PASSWORD_MIN_LENGTH = "رمز عبور باید حداقل ۸ کاراکتر باشد",
|
||||||
|
DOMAIN_ID_STRING = "شناسه دامنه باید یک رشته باشد",
|
||||||
|
DOMAIN_ID_REQUIRED = "شناسه دامنه الزامی است",
|
||||||
|
DOMAIN_ID_UUID = "شناسه دامنه باید یک یو یو آی دی باشد",
|
||||||
|
DISPLAY_NAME_STRING = "نام نمایشی باید یک رشته باشد",
|
||||||
|
DISPLAY_NAME_OPTIONAL = "نام نمایشی اختیاری است",
|
||||||
|
QUOTA_NUMBER = "حجم ایمیل باید یک عدد باشد",
|
||||||
|
QUOTA_OPTIONAL = "حجم ایمیل اختیاری است",
|
||||||
|
QUOTA_NOT_EMPTY = "حجم ایمیل نمیتواند خالی باشد",
|
||||||
|
ALIASES_ARRAY = "الیاس ها باید یک آرایه باشد",
|
||||||
|
ALIASES_STRING = "الیاس ها باید یک رشته باشد",
|
||||||
|
ALIASES_OPTIONAL = "الیاس ها اختیاری است",
|
||||||
|
TITLE_STRING = "عنوان باید یک رشته باشد",
|
||||||
|
TITLE_OPTIONAL = "عنوان اختیاری است",
|
||||||
|
TITLE_NOT_EMPTY = "عنوان نمیتواند خالی باشد",
|
||||||
|
EMAIL_USER_CREATED_SUCCESSFULLY = "ایمیل با موفقیت ایجاد شد",
|
||||||
|
STATUS_UPDATED = "وضعیت کاربر با موفقیت تغییر کرد",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum CommonMessage {
|
export const enum CommonMessage {
|
||||||
@@ -220,3 +244,7 @@ export const enum DnsRecordMessage {
|
|||||||
DNS_RECORD_NOT_FOUND = "رکورد DNS یافت نشد",
|
DNS_RECORD_NOT_FOUND = "رکورد DNS یافت نشد",
|
||||||
DNS_RECORD_DELETED = "رکورد DNS با موفقیت حذف شد",
|
DNS_RECORD_DELETED = "رکورد DNS با موفقیت حذف شد",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum RoleMessage {
|
||||||
|
NOT_FOUND = "نقش یافت نشد",
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ async function bootstrap() {
|
|||||||
done(null);
|
done(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastifyAdapter.getInstance().addContentTypeParser("text/plain", (_request, _payload, done) => {
|
|
||||||
done(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(AppModule, fastifyAdapter);
|
const app = await NestFactory.create<NestFastifyApplication>(AppModule, fastifyAdapter);
|
||||||
|
|
||||||
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }));
|
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }));
|
||||||
|
|||||||
@@ -34,15 +34,15 @@ export class DomainsController {
|
|||||||
@Patch(":id")
|
@Patch(":id")
|
||||||
@ApiOperation({ summary: "Update domain" })
|
@ApiOperation({ summary: "Update domain" })
|
||||||
@ApiResponse({ status: 200, description: "Domain updated successfully" })
|
@ApiResponse({ status: 200, description: "Domain updated successfully" })
|
||||||
updateDomain(@Param() paramDto: ParamDto, @Body() updateDomainDto: UpdateDomainDto) {
|
updateDomain(@Param() paramDto: ParamDto, @Body() updateDomainDto: UpdateDomainDto, @BusinessDec("id") businessId: string) {
|
||||||
return this.domainsService.updateDomain(paramDto.id, updateDomainDto);
|
return this.domainsService.updateDomain(paramDto.id, updateDomainDto, businessId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(":id")
|
@Delete(":id")
|
||||||
@ApiOperation({ summary: "Delete domain" })
|
@ApiOperation({ summary: "Delete domain" })
|
||||||
@ApiResponse({ status: 200, description: "Domain deleted successfully" })
|
@ApiResponse({ status: 200, description: "Domain deleted successfully" })
|
||||||
deleteDomain(@Param() paramDto: ParamDto) {
|
deleteDomain(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||||
return this.domainsService.deleteDomain(paramDto.id);
|
return this.domainsService.deleteDomain(paramDto.id, businessId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("setup")
|
@Get("setup")
|
||||||
|
|||||||
@@ -74,8 +74,11 @@ export class DomainsService {
|
|||||||
/**
|
/**
|
||||||
* Get domain by ID
|
* Get domain by ID
|
||||||
*/
|
*/
|
||||||
async getDomainById(id: string): Promise<Domain> {
|
async getDomainById(id: string, businessId: string): Promise<Domain> {
|
||||||
const domain = await this.domainRepository.findOne({ id, deletedAt: null }, { populate: ["business", "dnsRecords"] });
|
const domain = await this.domainRepository.findOne(
|
||||||
|
{ id, deletedAt: null, business: { id: businessId } },
|
||||||
|
{ populate: ["business", "dnsRecords"] },
|
||||||
|
);
|
||||||
|
|
||||||
if (!domain) throw new NotFoundException(DomainMessage.DOMAIN_NOT_FOUND);
|
if (!domain) throw new NotFoundException(DomainMessage.DOMAIN_NOT_FOUND);
|
||||||
|
|
||||||
@@ -93,8 +96,8 @@ export class DomainsService {
|
|||||||
/**
|
/**
|
||||||
* Update domain
|
* Update domain
|
||||||
*/
|
*/
|
||||||
async updateDomain(id: string, updateDomainDto: UpdateDomainDto) {
|
async updateDomain(id: string, updateDomainDto: UpdateDomainDto, businessId: string) {
|
||||||
const domain = await this.getDomainById(id);
|
const domain = await this.getDomainById(id, businessId);
|
||||||
|
|
||||||
this.em.assign(domain, updateDomainDto);
|
this.em.assign(domain, updateDomainDto);
|
||||||
|
|
||||||
@@ -107,8 +110,8 @@ export class DomainsService {
|
|||||||
/**
|
/**
|
||||||
* Delete domain
|
* Delete domain
|
||||||
*/
|
*/
|
||||||
async deleteDomain(id: string) {
|
async deleteDomain(id: string, businessId: string) {
|
||||||
const domain = await this.getDomainById(id);
|
const domain = await this.getDomainById(id, businessId);
|
||||||
|
|
||||||
// Soft delete
|
// Soft delete
|
||||||
domain.deletedAt = new Date();
|
domain.deletedAt = new Date();
|
||||||
@@ -122,8 +125,8 @@ export class DomainsService {
|
|||||||
/**
|
/**
|
||||||
* Verify domain ownership
|
* Verify domain ownership
|
||||||
*/
|
*/
|
||||||
async verifyDomain(id: string) {
|
async verifyDomain(id: string, businessId: string) {
|
||||||
const domain = await this.getDomainById(id);
|
const domain = await this.getDomainById(id, businessId);
|
||||||
|
|
||||||
// Check DNS records for verification
|
// Check DNS records for verification
|
||||||
const isVerified = await this.dnsService.verifyDomainDNS(domain);
|
const isVerified = await this.dnsService.verifyDomainDNS(domain);
|
||||||
@@ -208,7 +211,7 @@ export class DomainsService {
|
|||||||
const businessDomain = await this.domainRepository.findOne({ business: { id: businessId }, deletedAt: null });
|
const businessDomain = await this.domainRepository.findOne({ business: { id: businessId }, deletedAt: null });
|
||||||
if (!businessDomain) throw new NotFoundException(DomainMessage.DOMAIN_NOT_FOUND);
|
if (!businessDomain) throw new NotFoundException(DomainMessage.DOMAIN_NOT_FOUND);
|
||||||
|
|
||||||
const domain = await this.getDomainById(businessDomain.id);
|
const domain = await this.getDomainById(businessDomain.id, businessId);
|
||||||
|
|
||||||
const { dnsRecords } = await this.dnsService.getDomainDnsRecords(businessDomain.id);
|
const { dnsRecords } = await this.dnsService.getDomainDnsRecords(businessDomain.id);
|
||||||
|
|
||||||
@@ -222,7 +225,7 @@ export class DomainsService {
|
|||||||
const businessDomain = await this.domainRepository.findOne({ business: { id: businessId }, deletedAt: null });
|
const businessDomain = await this.domainRepository.findOne({ business: { id: businessId }, deletedAt: null });
|
||||||
if (!businessDomain) throw new BadRequestException(DomainMessage.DOMAIN_NOT_FOUND);
|
if (!businessDomain) throw new BadRequestException(DomainMessage.DOMAIN_NOT_FOUND);
|
||||||
|
|
||||||
const domain = await this.getDomainById(businessDomain.id);
|
const domain = await this.getDomainById(businessDomain.id, businessId);
|
||||||
|
|
||||||
const { overallStatus, recommendations } = await this.checkDomainVerificationStatus(businessId);
|
const { overallStatus, recommendations } = await this.checkDomainVerificationStatus(businessId);
|
||||||
|
|
||||||
@@ -241,10 +244,10 @@ export class DomainsService {
|
|||||||
const businessDomain = await this.domainRepository.findOne({ business: { id: businessId }, deletedAt: null });
|
const businessDomain = await this.domainRepository.findOne({ business: { id: businessId }, deletedAt: null });
|
||||||
if (!businessDomain) throw new BadRequestException(DomainMessage.DOMAIN_NOT_FOUND);
|
if (!businessDomain) throw new BadRequestException(DomainMessage.DOMAIN_NOT_FOUND);
|
||||||
|
|
||||||
const domain = await this.getDomainById(businessDomain.id);
|
const domain = await this.getDomainById(businessDomain.id, businessId);
|
||||||
|
|
||||||
// Trigger DNS verification
|
// Trigger DNS verification
|
||||||
await this.verifyDomain(domain.id);
|
await this.verifyDomain(domain.id, businessId);
|
||||||
|
|
||||||
const { dnsRecords } = await this.dnsService.getDomainDnsRecords(domain.id);
|
const { dnsRecords } = await this.dnsService.getDomainDnsRecords(domain.id);
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +1,326 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { IsArray, IsBoolean, IsEmail, IsNumber, IsOptional, IsString, IsUrl } from "class-validator";
|
import { Type } from "class-transformer";
|
||||||
|
import { IsArray, IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
|
class MailboxesDto {
|
||||||
|
@ApiPropertyOptional({ description: "Sent messages mailbox", example: "string" })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
sent?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: "Trash mailbox", example: "string" })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
trash?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: "Junk/Spam mailbox", example: "string" })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
junk?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: "Drafts mailbox", example: "string" })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
drafts?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FeatureFlagsDto {
|
||||||
|
@ApiPropertyOptional({ description: "Enable indexing feature", example: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
indexing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class CreateUserDto {
|
export class CreateUserDto {
|
||||||
@ApiProperty({ description: "Username for the user (letters and numbers only)", example: "testuser" })
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ApiProperty({ description: "This is not an email address but authentication username, use only letters and numbers", example: "testuser" })
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
@ApiProperty({ description: "Password for the user", example: "secretpassword123" })
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ApiProperty({ description: "Password for the user. You can use false to create an account without a password", example: "secretpass" })
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Main email address for the user", example: "testuser@example.com" })
|
@ApiPropertyOptional({
|
||||||
|
description: "Bcrypt hash for password. If provided, plain text password is ignored",
|
||||||
|
example: false,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
hashedPassword?: string | boolean;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Allow weak passwords",
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
allowUnsafe?: boolean;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Main email address for the user. If address is not set then a new one is generated based on the username and current domain name",
|
||||||
|
example: "testuser@example.com",
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
address?: string;
|
address?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "If true, do not set up an address for the user", default: false })
|
@ApiPropertyOptional({
|
||||||
|
description: "If true, then do not set up an address for the user",
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
emptyAddress?: boolean;
|
emptyAddress?: boolean;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Display name for the user", example: "Test User" })
|
@ApiPropertyOptional({
|
||||||
|
description: 'Language code for the user, eg. "en" or "et". Mailbox names for the default mailboxes (eg. "Trash") depend on the language',
|
||||||
|
example: "en",
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
language?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Default retention time in ms for mailboxes. Messages in Trash and Junk folders have a capped retention time of 30 days",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
retention?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Name for the user",
|
||||||
|
example: "Test User",
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: "Array of email addresses to forward all messages to",
|
description: "List of forwarding targets",
|
||||||
example: ["forward1@example.com", "forward2@example.com"],
|
example: ["forward@example.com"],
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsEmail({}, { each: true })
|
@IsString({ each: true })
|
||||||
forward?: string[];
|
targets?: string[];
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "URL to upload all messages to", example: "https://example.com/webhook" })
|
@ApiPropertyOptional({
|
||||||
|
description: "Relay server for outbound messages",
|
||||||
|
example: "smtp://relay.example.com:587",
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsUrl()
|
@IsString()
|
||||||
targetUrl?: string;
|
mtaRelay?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Maximum storage in bytes allowed for this user", example: 1073741824 })
|
@ApiPropertyOptional({
|
||||||
|
description: "Spam level threshold (0-100)",
|
||||||
|
example: 50,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
spamLevel?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum storage in bytes allowed for this user. If not set then the default value is used",
|
||||||
|
example: 1073741824,
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
quota?: number;
|
quota?: number;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Default retention time in milliseconds for mailboxes", example: 2592000000 })
|
@ApiPropertyOptional({
|
||||||
@IsOptional()
|
description: "Maximum number of recipients allowed to send mail to in a 24h window. Requires ZoneMTA with the Wild Duck plugin",
|
||||||
@IsNumber()
|
example: 2000,
|
||||||
retention?: number;
|
})
|
||||||
|
|
||||||
@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: 2000 })
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
recipients?: number;
|
recipients?: number;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Maximum number of forwarded emails in 24h", example: 2000 })
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum number of forwarded emails in a 24h window. Requires ZoneMTA with the Wild Duck plugin",
|
||||||
|
example: 2000,
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
forwards?: number;
|
forwards?: number;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Array of tags to associate with the user", example: ["vip", "customer"] })
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum number of filters allowed for this user",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
filters?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "If true then requires the user to change password on next login",
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
requirePasswordChange?: boolean;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum upload size per day for IMAP",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
imapMaxUpload?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum download size per day for IMAP",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
imapMaxDownload?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum download size per day for POP3",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
pop3MaxDownload?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum number of messages to download for POP3",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
pop3MaxMessages?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum number of parallel IMAP connections allowed",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
imapMaxConnections?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Maximum number of messages received per day",
|
||||||
|
example: 0,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
receivedMax?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "List of allowed From addresses",
|
||||||
|
example: ["allowed@example.com"],
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
fromWhitelist?: string[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Array of tags to be associated with that user. Tags can be used for filtering",
|
||||||
|
example: ["vip", "customer"],
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsString({ each: true })
|
@IsString({ each: true })
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "PGP public key for encryption" })
|
@ApiPropertyOptional({
|
||||||
|
description: "If true then autogenerated tags are added to the main address",
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
addTagsToAddress?: boolean;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "If true then sent messages are uploaded to Sent folder",
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
uploadSentMessages?: boolean;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Mailbox configuration",
|
||||||
|
type: MailboxesDto,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => MailboxesDto)
|
||||||
|
mailboxes?: MailboxesDto;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "List of disabled scopes for this user",
|
||||||
|
example: ["imap"],
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
disabledScopes?: string[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Custom metadata object",
|
||||||
|
example: {},
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
metaData?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Internal data object",
|
||||||
|
example: {},
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
internalData?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Optional PGP public key. Wild Duck uses OpenPGP.js so keys using EC might not pass validation",
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
pubKey?: string;
|
pubKey?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Whether to encrypt stored messages", default: false })
|
@ApiPropertyOptional({
|
||||||
|
description: "Indicates if messages stored should be encrypted using the PGP key",
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
encryptMessages?: boolean;
|
encryptMessages?: boolean;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Whether to encrypt forwarded messages", default: false })
|
@ApiPropertyOptional({
|
||||||
|
description: "Indicates if forwarded messages should be encrypted using the PGP key. Encryption applies after filter rules",
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
encryptForwarded?: boolean;
|
encryptForwarded?: boolean;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: "Session identifier for logging" })
|
@ApiPropertyOptional({
|
||||||
|
description: "Feature flags configuration",
|
||||||
|
type: FeatureFlagsDto,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => FeatureFlagsDto)
|
||||||
|
featureFlags?: FeatureFlagsDto;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Optional string of user session to be stored in security log",
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
sess?: string;
|
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()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
ip?: string;
|
ip?: string;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class WildDuckSettingsService extends WildDuckBaseService {
|
|||||||
/**
|
/**
|
||||||
* Set server setting value
|
* Set server setting value
|
||||||
*/
|
*/
|
||||||
setSetting(key: string, value: any): Observable<WildDuckSuccessResponse> {
|
setSetting(key: string, value: unknown): Observable<WildDuckSuccessResponse> {
|
||||||
return this.post<WildDuckSuccessResponse>(`/settings/${key}`, { value });
|
return this.post<WildDuckSuccessResponse>(`/settings/${key}`, { value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,50 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
import { Transform } from "class-transformer";
|
import { Transform } from "class-transformer";
|
||||||
import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString, Matches, MinLength } from "class-validator";
|
import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, Matches, MinLength } from "class-validator";
|
||||||
|
|
||||||
|
import { UserMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
export class CreateEmailUserDto {
|
export class CreateEmailUserDto {
|
||||||
@ApiProperty({
|
@IsNotEmpty({ message: UserMessage.USERNAME_REQUIRED })
|
||||||
description: "Username part of the email address (before @domain.com)",
|
@IsString({ message: UserMessage.USERNAME_STRING })
|
||||||
example: "john.doe",
|
@MinLength(2, { message: UserMessage.USERNAME_MIN_LENGTH })
|
||||||
})
|
@Matches(/^[a-zA-Z0-9._-]+$/, { message: UserMessage.USERNAME_MATCHES })
|
||||||
@IsString()
|
@ApiProperty({ description: "Username part of the email address (before @domain.com)", example: "john.doe" })
|
||||||
@IsNotEmpty()
|
|
||||||
@MinLength(2)
|
|
||||||
@Matches(/^[a-zA-Z0-9._-]+$/, {
|
|
||||||
message: "Username can only contain letters, numbers, dots, underscores, and hyphens",
|
|
||||||
})
|
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@IsNotEmpty({ message: UserMessage.PASSWORD_REQUIRED })
|
||||||
description: "User password for email account",
|
@IsString({ message: UserMessage.PASSWORD_STRING })
|
||||||
example: "SecurePassword123!",
|
@MinLength(8, { message: UserMessage.PASSWORD_MIN_LENGTH })
|
||||||
minLength: 8,
|
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, { message: UserMessage.PASSWORD_MATCHES })
|
||||||
})
|
@ApiProperty({ description: "User password for email account", example: "SecurePassword123!", minLength: 8 })
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
@MinLength(8)
|
|
||||||
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, {
|
|
||||||
message: "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character",
|
|
||||||
})
|
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@IsNotEmpty({ message: UserMessage.DOMAIN_ID_REQUIRED })
|
||||||
description: "Domain ID for the email address",
|
@IsUUID("7", { message: UserMessage.DOMAIN_ID_UUID })
|
||||||
example: "12345678-1234-1234-1234-123456789012",
|
@ApiProperty({ description: "Domain ID for the email address", example: "12345678-1234-1234-1234-123456789012" })
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
domainId: string;
|
domainId: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({ description: "Display name for the user", example: "John Doe" })
|
||||||
description: "Display name for the user",
|
@IsString({ message: UserMessage.DISPLAY_NAME_STRING })
|
||||||
example: "John Doe",
|
@IsOptional({ message: UserMessage.DISPLAY_NAME_OPTIONAL })
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@Transform(({ value }) => value?.trim())
|
@Transform(({ value }) => value?.trim())
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
@IsOptional({ message: UserMessage.QUOTA_OPTIONAL })
|
||||||
description: "Email quota in bytes (default: 1GB)",
|
@IsNotEmpty({ message: UserMessage.QUOTA_NOT_EMPTY })
|
||||||
example: 1073741824,
|
@IsNumber({ allowNaN: false, allowInfinity: false }, { message: UserMessage.QUOTA_NUMBER })
|
||||||
})
|
@ApiPropertyOptional({ description: "Email quota in bytes (default: 1GB)", example: 1073741824 })
|
||||||
@IsNumber()
|
|
||||||
@IsOptional()
|
|
||||||
quota?: number;
|
quota?: number;
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
@IsArray({ message: UserMessage.ALIASES_ARRAY })
|
||||||
description: "Additional email aliases for this user",
|
@IsString({ each: true, message: UserMessage.ALIASES_STRING })
|
||||||
example: ["johndoe@example.com", "j.doe@example.com"],
|
@IsOptional({ message: UserMessage.ALIASES_OPTIONAL })
|
||||||
})
|
@ApiPropertyOptional({ description: "Additional email aliases for this user", example: ["johndoe@example.com", "j.doe@example.com"] })
|
||||||
@IsArray()
|
|
||||||
@IsString({ each: true })
|
|
||||||
@IsOptional()
|
|
||||||
aliases?: string[];
|
aliases?: string[];
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
@IsNotEmpty({ message: UserMessage.TITLE_NOT_EMPTY })
|
||||||
description: "User role/title",
|
@IsString({ message: UserMessage.TITLE_STRING })
|
||||||
example: "Marketing Manager",
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@Transform(({ value }) => value?.trim())
|
@Transform(({ value }) => value?.trim())
|
||||||
title?: string;
|
@ApiProperty({ description: "User role/title", example: "Marketing Manager" })
|
||||||
|
title: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export class User extends BaseEntity {
|
|||||||
@ManyToOne(() => Business, { deleteRule: "restrict" })
|
@ManyToOne(() => Business, { deleteRule: "restrict" })
|
||||||
business!: Business;
|
business!: Business;
|
||||||
|
|
||||||
@ManyToOne(() => Domain, { nullable: true })
|
@ManyToOne(() => Domain, { deleteRule: "restrict" })
|
||||||
domain?: Domain; // The domain this email user belongs to
|
domain!: Domain; // The domain this email user belongs to
|
||||||
|
|
||||||
[EntityRepositoryType]?: UserRepository;
|
[EntityRepositoryType]?: UserRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
DANAK_SMTPS_PORT,
|
DANAK_SMTPS_PORT,
|
||||||
DANAK_SMTPS_SERVER,
|
DANAK_SMTPS_SERVER,
|
||||||
} from "../../../common/constants";
|
} from "../../../common/constants";
|
||||||
import { BusinessMessage, DomainMessage, MailServerMessage, UserMessage } from "../../../common/enums/message.enum";
|
import { BusinessMessage, DomainMessage, MailServerMessage, RoleMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||||
import { Business } from "../../businesses/entities/business.entity";
|
import { Business } from "../../businesses/entities/business.entity";
|
||||||
import { DomainStatus } from "../../domains/enums/domain-status.enum";
|
import { DomainStatus } from "../../domains/enums/domain-status.enum";
|
||||||
import { DomainsService } from "../../domains/services/domains.service";
|
import { DomainsService } from "../../domains/services/domains.service";
|
||||||
@@ -66,29 +66,21 @@ export class UsersService {
|
|||||||
const business = await this.em.findOne(Business, { id: businessId });
|
const business = await this.em.findOne(Business, { id: businessId });
|
||||||
if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND);
|
if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND);
|
||||||
|
|
||||||
const domain = await this.domainsService.getDomainById(domainId);
|
const domain = await this.domainsService.getDomainById(domainId, businessId);
|
||||||
|
|
||||||
if (domain.business.id !== businessId) {
|
if (domain.business.id !== businessId) throw new BadRequestException(DomainMessage.NOT_BELONG_TO_BUSINESS);
|
||||||
throw new BadRequestException(DomainMessage.NOT_BELONG_TO_BUSINESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domain.status !== DomainStatus.VERIFIED) {
|
if (domain.status !== DomainStatus.VERIFIED) throw new BadRequestException(DomainMessage.NOT_VERIFIED);
|
||||||
throw new BadRequestException(DomainMessage.NOT_VERIFIED);
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailAddress = `${username}@${domain.name}`;
|
const emailAddress = `${username}@${domain.name}`;
|
||||||
|
|
||||||
// Check if email address already exists
|
|
||||||
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);
|
||||||
|
|
||||||
const hashedPassword = await this.passwordService.hashPassword(password);
|
const hashedPassword = await this.passwordService.hashPassword(password);
|
||||||
|
|
||||||
let userRole = await this.em.findOne(Role, { name: RoleEnum.USER });
|
const userRole = await this.em.findOne(Role, { name: RoleEnum.USER });
|
||||||
if (!userRole) {
|
if (!userRole) throw new BadRequestException(RoleMessage.NOT_FOUND);
|
||||||
userRole = this.em.create(Role, { name: RoleEnum.USER });
|
|
||||||
await this.em.persistAndFlush(userRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mailServerUser = await firstValueFrom(
|
const mailServerUser = await firstValueFrom(
|
||||||
@@ -97,8 +89,8 @@ export class UsersService {
|
|||||||
password,
|
password,
|
||||||
address: emailAddress,
|
address: emailAddress,
|
||||||
name: displayName || username,
|
name: displayName || username,
|
||||||
quota: quota || 1073741824, // 1GB default
|
quota: quota || 1073741824,
|
||||||
tags: ["dmail-user"],
|
language: "fa",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -121,7 +113,6 @@ export class UsersService {
|
|||||||
|
|
||||||
await this.em.persistAndFlush(user);
|
await this.em.persistAndFlush(user);
|
||||||
|
|
||||||
// Create aliases if provided
|
|
||||||
if (aliases && aliases.length > 0) {
|
if (aliases && aliases.length > 0) {
|
||||||
for (const alias of aliases) {
|
for (const alias of aliases) {
|
||||||
try {
|
try {
|
||||||
@@ -164,7 +155,7 @@ export class UsersService {
|
|||||||
encryption: DANAK_SMTPS_ENCRYPTION,
|
encryption: DANAK_SMTPS_ENCRYPTION,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
message: "Email user created successfully",
|
message: UserMessage.EMAIL_USER_CREATED_SUCCESSFULLY,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to create email user ${emailAddress}:`, error);
|
this.logger.error(`Failed to create email user ${emailAddress}:`, error);
|
||||||
@@ -252,4 +243,19 @@ export class UsersService {
|
|||||||
throw new BadRequestException(UserMessage.FAILED_TO_UPDATE_EMAIL_USER_QUOTA);
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
|
||||||
import { ApiHeader, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||||
|
|
||||||
import { CreateEmailUserDto } from "./DTO/create-email-user.dto";
|
import { CreateEmailUserDto } from "./DTO/create-email-user.dto";
|
||||||
import { UsersService } from "./services/users.service";
|
import { UsersService } from "./services/users.service";
|
||||||
@@ -7,8 +7,7 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
|||||||
import { BusinessDec } from "../../common/decorators/business.decorator";
|
import { BusinessDec } from "../../common/decorators/business.decorator";
|
||||||
import { ParamDto } from "../../common/DTO/param.dto";
|
import { ParamDto } from "../../common/DTO/param.dto";
|
||||||
|
|
||||||
@ApiTags("email-users")
|
@Controller("users")
|
||||||
@Controller("email-users")
|
|
||||||
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
export class UsersController {
|
export class UsersController {
|
||||||
@@ -41,4 +40,11 @@ export class UsersController {
|
|||||||
updateEmailUserQuota(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string, @Body() body: { quota: number }) {
|
updateEmailUserQuota(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string, @Body() body: { quota: number }) {
|
||||||
return this.usersService.updateEmailUserQuota(paramDto.id, businessId, body.quota);
|
return this.usersService.updateEmailUserQuota(paramDto.id, businessId, body.quota);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Patch(":id/toggle-status")
|
||||||
|
@ApiOperation({ summary: "Update email user status" })
|
||||||
|
@ApiResponse({ status: 200, description: "Email user status updated successfully" })
|
||||||
|
updateEmailUserStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.usersService.updateEmailUserStatus(paramDto.id, businessId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user