chore: new feture for quato

This commit is contained in:
mahyargdz
2025-07-07 16:21:30 +03:30
parent 6565d2ae8b
commit b66114c900
21 changed files with 815 additions and 98 deletions
@@ -0,0 +1,19 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsIn, IsOptional, IsString } from "class-validator";
import { PaginationDto } from "../../../common/DTO/pagination.dto";
import { CommonMessage } from "../../../common/enums/message.enum";
export class UserListQueryDto extends PaginationDto {
@IsOptional()
@IsString({ message: CommonMessage.SEARCH_QUERY_STRING })
@ApiPropertyOptional({ description: "Search query", example: "search query" })
q?: string;
@IsOptional()
@Type(() => Number)
@IsIn([1, 0], { message: CommonMessage.IS_ACTIVE_SHOULD_BE_1_0 })
@ApiPropertyOptional({ description: "User status", example: 1 })
isActive?: number;
}
+14 -2
View File
@@ -1,10 +1,11 @@
import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, Property } from "@mikro-orm/core";
import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, OneToOne, Property } from "@mikro-orm/core";
import { RefreshToken } from "./refresh-token.entity";
import { Role } from "./role.entity";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Business } from "../../businesses/entities/business.entity";
import { Domain } from "../../domains/entities/domain.entity";
import { EmailSignature } from "../../email-signatures/entities/email-signature.entity";
import { UserRepository } from "../repositories/user.repository";
@Entity({ repository: () => UserRepository })
@@ -27,18 +28,29 @@ export class User extends BaseEntity {
@Property({ type: "bigint", nullable: true, default: 1073741824 }) // 1GB default
emailQuota?: number; // Email quota in bytes
@Property({ type: "bigint", nullable: true, default: 0 })
emailQuotaUsed?: number; // Email quota used in bytes
// Email account integration fields
@Property({ type: "varchar", length: 255, nullable: true })
emailAccountId?: string; // WildDuck user ID
wildduckUserId?: string; // WildDuck user ID
@Property({ type: "varchar", length: 255, nullable: true })
displayName?: string; // Display name for email
@Property({ type: "timestamptz", nullable: true })
lastQuotaSync?: Date; // Last time quota was synced with WildDuck
@Property({ default: true, nullable: false })
isActive: boolean = true;
//=========================
@OneToOne(() => EmailSignature, (signature) => signature.user, { nullable: true, deleteRule: "cascade" })
emailSignature?: EmailSignature;
//=========================
@OneToMany(() => RefreshToken, (token) => token.user)
refreshTokens = new Collection<RefreshToken>(this);
@@ -1,5 +1,38 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { EntityRepository, FilterQuery } from "@mikro-orm/postgresql";
import { PaginationUtils } from "../../utils/services/pagination.utils";
import { UserListQueryDto } from "../DTO/user-list-query.dto";
import { User } from "../entities/user.entity";
export class UserRepository extends EntityRepository<User> {}
export class UserRepository extends EntityRepository<User> {
async getUserListForAdmin(businessId: string, queryDto: UserListQueryDto) {
const { limit, skip } = PaginationUtils(queryDto);
const whereClause: FilterQuery<User> = {
business: { id: businessId },
emailEnabled: true,
deletedAt: null,
};
if (queryDto.q) {
whereClause.$or = [
{ userName: { $like: `%${queryDto.q}%` } },
{ emailAddress: { $like: `%${queryDto.q}%` } },
{ displayName: { $like: `%${queryDto.q}%` } },
];
}
if (queryDto.isActive) {
console.log(queryDto.isActive);
whereClause.isActive = queryDto.isActive === 1;
}
return this.findAndCount(whereClause, {
populate: ["domain", "business"],
limit,
offset: skip,
orderBy: { createdAt: "DESC" },
});
}
}
+11 -21
View File
@@ -1,4 +1,4 @@
import { EntityManager, FilterQuery } from "@mikro-orm/postgresql";
import { EntityManager } from "@mikro-orm/postgresql";
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
import { firstValueFrom } from "rxjs";
@@ -17,6 +17,7 @@ import { DomainsService } from "../../domains/services/domains.service";
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 { UserListQueryDto } from "../DTO/user-list-query.dto";
import { Role } from "../entities/role.entity";
import { User } from "../entities/user.entity";
import { RoleEnum } from "../enums/role.enum";
@@ -103,7 +104,7 @@ export class UsersService {
emailAddress,
emailEnabled: true,
emailQuota: quota || 1073741824,
emailAccountId: mailServerUser.id,
wildduckUserId: mailServerUser.id,
displayName: displayName || username,
isActive: true,
business,
@@ -135,7 +136,7 @@ export class UsersService {
id: user.id,
emailAddress: user.emailAddress!,
displayName: user.displayName!,
emailAccountId: user.emailAccountId!,
wildduckUserId: user.wildduckUserId!,
emailQuota: user.emailQuota!,
emailEnabled: user.emailEnabled,
domain: domain.name,
@@ -164,21 +165,10 @@ export class UsersService {
}
/*******************************/
async getEmailUsers(businessId: string, domainId?: string) {
const whereClause: FilterQuery<User> = {
business: { id: businessId },
emailEnabled: true,
deletedAt: null,
};
async getEmailUsers(businessId: string, queryDto: UserListQueryDto) {
const [users, count] = await this.userRepository.getUserListForAdmin(businessId, queryDto);
if (domainId) whereClause.domain = { id: domainId };
const users = await this.userRepository.find(whereClause, {
populate: ["domain", "business"],
orderBy: { createdAt: "DESC" },
});
return { users };
return { users, count, paginate: true };
}
/*******************************/
@@ -192,8 +182,8 @@ export class UsersService {
if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND);
try {
if (user.emailAccountId) {
await firstValueFrom(this.mailServerService.users.deleteUser(user.emailAccountId));
if (user.wildduckUserId) {
await firstValueFrom(this.mailServerService.users.deleteUser(user.wildduckUserId));
}
user.deletedAt = new Date();
@@ -224,9 +214,9 @@ export class UsersService {
if (!user) throw new NotFoundException(UserMessage.EMAIL_USER_NOT_FOUND);
try {
if (user.emailAccountId) {
if (user.wildduckUserId) {
await firstValueFrom(
this.mailServerService.users.updateUser(user.emailAccountId, {
this.mailServerService.users.updateUser(user.wildduckUserId, {
quota: newQuota,
}),
);
+6 -3
View File
@@ -1,14 +1,17 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
import { CreateEmailUserDto } from "./DTO/create-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";
import { BusinessDec } from "../../common/decorators/business.decorator";
import { ParamDto } from "../../common/DTO/param.dto";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
@Controller("users")
@ApiHeader({ name: "x-business-id", description: "Business ID" })
@UseInterceptors(BusinessInterceptor)
@AuthGuards()
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@@ -23,8 +26,8 @@ export class UsersController {
@Get()
@ApiOperation({ summary: "Get email users for business" })
@ApiResponse({ status: 200, description: "Email users retrieved successfully" })
getEmailUsers(@BusinessDec("id") businessId: string, @Query("domainId") domainId?: string) {
return this.usersService.getEmailUsers(businessId, domainId);
getEmailUsers(@BusinessDec("id") businessId: string, @Query() queryDto: UserListQueryDto) {
return this.usersService.getEmailUsers(businessId, queryDto);
}
@Delete(":id")
+2 -1
View File
@@ -6,12 +6,13 @@ import { Role } from "./entities/role.entity";
import { User } from "./entities/user.entity";
import { UsersService } from "./services/users.service";
import { UsersController } from "./users.controller";
import { BusinessesModule } from "../businesses/businesses.module";
import { DomainsModule } from "../domains/domains.module";
import { MailServerModule } from "../mail-server/mail-server.module";
import { UtilsModule } from "../utils/utils.module";
@Module({
imports: [MikroOrmModule.forFeature([User, RefreshToken, Role]), UtilsModule, MailServerModule, DomainsModule],
imports: [MikroOrmModule.forFeature([User, RefreshToken, Role]), UtilsModule, MailServerModule, DomainsModule, BusinessesModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],