chore: change the quota sync to the queue
This commit is contained in:
@@ -51,7 +51,6 @@
|
|||||||
"@nestjs/passport": "^11.0.5",
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-fastify": "^11.1.3",
|
"@nestjs/platform-fastify": "^11.1.3",
|
||||||
"@nestjs/platform-socket.io": "^11.1.3",
|
"@nestjs/platform-socket.io": "^11.1.3",
|
||||||
"@nestjs/schedule": "^6.0.0",
|
|
||||||
"@nestjs/swagger": "^11.2.0",
|
"@nestjs/swagger": "^11.2.0",
|
||||||
"@nestjs/throttler": "^6.4.0",
|
"@nestjs/throttler": "^6.4.0",
|
||||||
"@nestjs/websockets": "^11.1.3",
|
"@nestjs/websockets": "^11.1.3",
|
||||||
|
|||||||
Generated
-29
@@ -71,9 +71,6 @@ importers:
|
|||||||
'@nestjs/platform-socket.io':
|
'@nestjs/platform-socket.io':
|
||||||
specifier: ^11.1.3
|
specifier: ^11.1.3
|
||||||
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(rxjs@7.8.2)
|
version: 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(rxjs@7.8.2)
|
||||||
'@nestjs/schedule':
|
|
||||||
specifier: ^6.0.0
|
|
||||||
version: 6.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)
|
|
||||||
'@nestjs/swagger':
|
'@nestjs/swagger':
|
||||||
specifier: ^11.2.0
|
specifier: ^11.2.0
|
||||||
version: 11.2.0(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
|
version: 11.2.0(@fastify/static@8.2.0)(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
|
||||||
@@ -1282,12 +1279,6 @@ packages:
|
|||||||
'@nestjs/websockets': ^11.0.0
|
'@nestjs/websockets': ^11.0.0
|
||||||
rxjs: ^7.1.0
|
rxjs: ^7.1.0
|
||||||
|
|
||||||
'@nestjs/schedule@6.0.0':
|
|
||||||
resolution: {integrity: sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@nestjs/common': ^10.0.0 || ^11.0.0
|
|
||||||
'@nestjs/core': ^10.0.0 || ^11.0.0
|
|
||||||
|
|
||||||
'@nestjs/schematics@11.0.5':
|
'@nestjs/schematics@11.0.5':
|
||||||
resolution: {integrity: sha512-T50SCNyqCZ/fDssaOD7meBKLZ87ebRLaJqZTJPvJKjlib1VYhMOCwXYsr7bjMPmuPgiQHOwvppz77xN/m6GM7A==}
|
resolution: {integrity: sha512-T50SCNyqCZ/fDssaOD7meBKLZ87ebRLaJqZTJPvJKjlib1VYhMOCwXYsr7bjMPmuPgiQHOwvppz77xN/m6GM7A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1606,9 +1597,6 @@ packages:
|
|||||||
'@types/jsonwebtoken@9.0.7':
|
'@types/jsonwebtoken@9.0.7':
|
||||||
resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==}
|
resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==}
|
||||||
|
|
||||||
'@types/luxon@3.6.2':
|
|
||||||
resolution: {integrity: sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==}
|
|
||||||
|
|
||||||
'@types/methods@1.1.4':
|
'@types/methods@1.1.4':
|
||||||
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
|
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
|
||||||
|
|
||||||
@@ -2524,10 +2512,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
cron@4.3.0:
|
|
||||||
resolution: {integrity: sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==}
|
|
||||||
engines: {node: '>=18.x'}
|
|
||||||
|
|
||||||
cross-spawn@6.0.6:
|
cross-spawn@6.0.6:
|
||||||
resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
|
resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
|
||||||
engines: {node: '>=4.8'}
|
engines: {node: '>=4.8'}
|
||||||
@@ -7191,12 +7175,6 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
'@nestjs/schedule@6.0.0(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.3)':
|
|
||||||
dependencies:
|
|
||||||
'@nestjs/common': 11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
|
||||||
'@nestjs/core': 11.1.3(@nestjs/common@11.1.3(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
|
||||||
cron: 4.3.0
|
|
||||||
|
|
||||||
'@nestjs/schematics@11.0.5(chokidar@4.0.3)(typescript@5.8.3)':
|
'@nestjs/schematics@11.0.5(chokidar@4.0.3)(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-devkit/core': 19.2.6(chokidar@4.0.3)
|
'@angular-devkit/core': 19.2.6(chokidar@4.0.3)
|
||||||
@@ -7514,8 +7492,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.16.0
|
'@types/node': 22.16.0
|
||||||
|
|
||||||
'@types/luxon@3.6.2': {}
|
|
||||||
|
|
||||||
'@types/methods@1.1.4': {}
|
'@types/methods@1.1.4': {}
|
||||||
|
|
||||||
'@types/mime@1.3.5': {}
|
'@types/mime@1.3.5': {}
|
||||||
@@ -8569,11 +8545,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
luxon: 3.6.1
|
luxon: 3.6.1
|
||||||
|
|
||||||
cron@4.3.0:
|
|
||||||
dependencies:
|
|
||||||
'@types/luxon': 3.6.2
|
|
||||||
luxon: 3.6.1
|
|
||||||
|
|
||||||
cross-spawn@6.0.6:
|
cross-spawn@6.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
nice-try: 1.0.5
|
nice-try: 1.0.5
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { CacheModule } from "@nestjs/cache-manager";
|
|||||||
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
||||||
import { ConfigModule } from "@nestjs/config";
|
import { ConfigModule } from "@nestjs/config";
|
||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
import { ScheduleModule } from "@nestjs/schedule";
|
|
||||||
import { ThrottlerModule } from "@nestjs/throttler";
|
import { ThrottlerModule } from "@nestjs/throttler";
|
||||||
|
|
||||||
import { bullMqConfig } from "./configs/bullmq.config";
|
import { bullMqConfig } from "./configs/bullmq.config";
|
||||||
@@ -30,7 +29,6 @@ import { UtilsModule } from "./modules/utils/utils.module";
|
|||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true, cache: true }),
|
ConfigModule.forRoot({ isGlobal: true, cache: true }),
|
||||||
JwtModule.registerAsync(jwtConfig()),
|
JwtModule.registerAsync(jwtConfig()),
|
||||||
ScheduleModule.forRoot(),
|
|
||||||
BullModule.forRootAsync(bullMqConfig()),
|
BullModule.forRootAsync(bullMqConfig()),
|
||||||
CacheModule.registerAsync(cacheConfig()),
|
CacheModule.registerAsync(cacheConfig()),
|
||||||
HttpModule.registerAsync(httpConfig()),
|
HttpModule.registerAsync(httpConfig()),
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export const QUOTA_SYNC_QUEUE = Object.freeze({
|
||||||
|
QUEUE_NAME: "quota-sync",
|
||||||
|
QUEUE_PREFIX: "quota",
|
||||||
|
|
||||||
|
// Job names
|
||||||
|
SYNC_USER_QUOTA_JOB: "sync.user.quota",
|
||||||
|
SYNC_BUSINESS_QUOTA_JOB: "sync.business.quota",
|
||||||
|
SYNC_ALL_USERS_QUOTA_JOB: "sync.all.users.quota",
|
||||||
|
SYNC_ALL_BUSINESSES_QUOTA_JOB: "sync.all.businesses.quota",
|
||||||
|
SYNC_COMPREHENSIVE_QUOTA_JOB: "sync.comprehensive.quota",
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export interface ISyncUserQuotaJob {
|
||||||
|
userId: string;
|
||||||
|
businessId: string;
|
||||||
|
wildduckUserId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISyncBusinessQuotaJob {
|
||||||
|
businessId: string;
|
||||||
|
businessName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ISyncAllUsersQuotaJob = Record<string, never>;
|
||||||
|
|
||||||
|
export type ISyncAllBusinessesQuotaJob = Record<string, never>;
|
||||||
|
|
||||||
|
export type ISyncComprehensiveQuotaJob = Record<string, never>;
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// import { EntityManager } from "@mikro-orm/core";
|
||||||
|
import { Processor } from "@nestjs/bullmq";
|
||||||
|
import { Logger } from "@nestjs/common";
|
||||||
|
import { Job } from "bullmq";
|
||||||
|
|
||||||
|
import { WorkerProcessor } from "../../../common/queues/worker.processor";
|
||||||
|
import { QUOTA_SYNC_QUEUE } from "../constants";
|
||||||
|
import {
|
||||||
|
ISyncAllBusinessesQuotaJob,
|
||||||
|
ISyncAllUsersQuotaJob,
|
||||||
|
ISyncBusinessQuotaJob,
|
||||||
|
ISyncComprehensiveQuotaJob,
|
||||||
|
ISyncUserQuotaJob,
|
||||||
|
} from "../interfaces/quota-sync-job.interface";
|
||||||
|
import { QuotaSyncService } from "../services/quota-sync.service";
|
||||||
|
|
||||||
|
@Processor(QUOTA_SYNC_QUEUE.QUEUE_NAME)
|
||||||
|
export class QuotaSyncProcessor extends WorkerProcessor {
|
||||||
|
protected readonly logger = new Logger(QuotaSyncProcessor.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly quotaSyncService: QuotaSyncService,
|
||||||
|
// private readonly em: EntityManager,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async process(job: Job): Promise<void> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.handleJob(job);
|
||||||
|
this.logJobSuccess(job, startTime);
|
||||||
|
} catch (error) {
|
||||||
|
this.logJobFailure(job, error, startTime);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleJob(job: Job): Promise<void> {
|
||||||
|
switch (job.name) {
|
||||||
|
case QUOTA_SYNC_QUEUE.SYNC_USER_QUOTA_JOB:
|
||||||
|
await this.handleSyncUserQuota(job);
|
||||||
|
break;
|
||||||
|
case QUOTA_SYNC_QUEUE.SYNC_BUSINESS_QUOTA_JOB:
|
||||||
|
await this.handleSyncBusinessQuota(job);
|
||||||
|
break;
|
||||||
|
case QUOTA_SYNC_QUEUE.SYNC_ALL_USERS_QUOTA_JOB:
|
||||||
|
await this.handleSyncAllUsersQuota(job);
|
||||||
|
break;
|
||||||
|
case QUOTA_SYNC_QUEUE.SYNC_ALL_BUSINESSES_QUOTA_JOB:
|
||||||
|
await this.handleSyncAllBusinessesQuota(job);
|
||||||
|
break;
|
||||||
|
case QUOTA_SYNC_QUEUE.SYNC_COMPREHENSIVE_QUOTA_JOB:
|
||||||
|
await this.handleSyncComprehensiveQuota(job);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported job type: ${job.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleSyncUserQuota(job: Job<ISyncUserQuotaJob>): Promise<void> {
|
||||||
|
const { userId } = job.data;
|
||||||
|
this.logger.log(`Processing user quota sync for user: ${userId}`);
|
||||||
|
|
||||||
|
await this.quotaSyncService.syncUserQuotaById(userId);
|
||||||
|
this.logger.log(`Successfully synced quota for user: ${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleSyncBusinessQuota(job: Job<ISyncBusinessQuotaJob>): Promise<void> {
|
||||||
|
const { businessId, businessName } = job.data;
|
||||||
|
this.logger.log(`Processing business quota sync for business: ${businessName} (${businessId})`);
|
||||||
|
|
||||||
|
await this.quotaSyncService.syncBusinessQuotaById(businessId);
|
||||||
|
this.logger.log(`Successfully synced quota for business: ${businessName} (${businessId})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleSyncAllUsersQuota(_job: Job<ISyncAllUsersQuotaJob>): Promise<void> {
|
||||||
|
this.logger.log("Processing quota sync for all users");
|
||||||
|
|
||||||
|
await this.quotaSyncService.syncAllUsersQuota();
|
||||||
|
this.logger.log("Successfully synced quota for all users");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleSyncAllBusinessesQuota(_job: Job<ISyncAllBusinessesQuotaJob>): Promise<void> {
|
||||||
|
this.logger.log("Processing quota sync for all businesses");
|
||||||
|
|
||||||
|
await this.quotaSyncService.syncAllBusinessQuotas();
|
||||||
|
this.logger.log("Successfully synced quota for all businesses");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleSyncComprehensiveQuota(_job: Job<ISyncComprehensiveQuotaJob>): Promise<void> {
|
||||||
|
this.logger.log("Processing comprehensive quota sync");
|
||||||
|
|
||||||
|
await this.quotaSyncService.syncAllQuotas();
|
||||||
|
this.logger.log("Successfully completed comprehensive quota sync");
|
||||||
|
}
|
||||||
|
|
||||||
|
private logJobSuccess(job: Job, startTime: number): void {
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
this.logger.log(`Job ${job.id} (${job.name}) completed successfully in ${duration}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private logJobFailure(job: Job, error: unknown, startTime: number): void {
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
this.logger.error(`Job ${job.id} (${job.name}) failed after ${duration}ms: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
|
import { BullModule } from "@nestjs/bullmq";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
import { MailServerModule } from "../mail-server/mail-server.module";
|
import { QUOTA_SYNC_QUEUE } from "./constants";
|
||||||
|
import { QuotaSyncProcessor } from "./queue/quota-sync.processor";
|
||||||
import { QuotaSyncService } from "./services/quota-sync.service";
|
import { QuotaSyncService } from "./services/quota-sync.service";
|
||||||
|
import { MailServerModule } from "../mail-server/mail-server.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [MailServerModule],
|
imports: [
|
||||||
providers: [QuotaSyncService],
|
MailServerModule,
|
||||||
|
BullModule.registerQueue({
|
||||||
|
name: QUOTA_SYNC_QUEUE.QUEUE_NAME,
|
||||||
|
prefix: QUOTA_SYNC_QUEUE.QUEUE_PREFIX,
|
||||||
|
defaultJobOptions: {
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [QuotaSyncService, QuotaSyncProcessor],
|
||||||
exports: [QuotaSyncService],
|
exports: [QuotaSyncService],
|
||||||
})
|
})
|
||||||
export class QuotaSyncModule {}
|
export class QuotaSyncModule {}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { EntityManager } from "@mikro-orm/core";
|
import { EntityManager } from "@mikro-orm/core";
|
||||||
|
import { InjectQueue } from "@nestjs/bullmq";
|
||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { Cron, CronExpression } from "@nestjs/schedule";
|
import { Queue } from "bullmq";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { Business } from "../../businesses/entities/business.entity";
|
import { Business } from "../../businesses/entities/business.entity";
|
||||||
import { WildDuckUsersService } from "../../mail-server/services/wildduck-users.service";
|
import { WildDuckUsersService } from "../../mail-server/services/wildduck-users.service";
|
||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
import { QUOTA_SYNC_QUEUE } from "../constants";
|
||||||
import {
|
import {
|
||||||
BusinessQuotaInfoDto,
|
BusinessQuotaInfoDto,
|
||||||
BusinessQuotaStatisticsDto,
|
BusinessQuotaStatisticsDto,
|
||||||
@@ -14,20 +16,127 @@ import {
|
|||||||
QuotaSyncResultDto,
|
QuotaSyncResultDto,
|
||||||
UserQuotaStatisticsDto,
|
UserQuotaStatisticsDto,
|
||||||
} from "../DTO/quota-statistics.dto";
|
} from "../DTO/quota-statistics.dto";
|
||||||
|
import { ISyncBusinessQuotaJob, ISyncUserQuotaJob } from "../interfaces/quota-sync-job.interface";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QuotaSyncService {
|
export class QuotaSyncService {
|
||||||
private readonly logger = new Logger(QuotaSyncService.name);
|
private readonly logger = new Logger(QuotaSyncService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@InjectQueue(QUOTA_SYNC_QUEUE.QUEUE_NAME) private readonly quotaSyncQueue: Queue,
|
||||||
private readonly wildDuckUsersService: WildDuckUsersService,
|
private readonly wildDuckUsersService: WildDuckUsersService,
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync quota usage every 2 hours
|
* Add a job to sync quota usage for all users
|
||||||
|
*/
|
||||||
|
async scheduleAllUsersQuotaSync(): Promise<void> {
|
||||||
|
await this.quotaSyncQueue.add(
|
||||||
|
QUOTA_SYNC_QUEUE.SYNC_ALL_USERS_QUOTA_JOB,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
attempts: 3,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 2000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.logger.log("Scheduled all users quota sync job");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a job to sync business quota usage for all businesses
|
||||||
|
*/
|
||||||
|
async scheduleAllBusinessQuotasSync(): Promise<void> {
|
||||||
|
await this.quotaSyncQueue.add(
|
||||||
|
QUOTA_SYNC_QUEUE.SYNC_ALL_BUSINESSES_QUOTA_JOB,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
attempts: 3,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 2000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.logger.log("Scheduled all businesses quota sync job");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a job to sync quota for a specific user
|
||||||
|
*/
|
||||||
|
async scheduleUserQuotaSync(userId: string, businessId: string, wildduckUserId: string): Promise<void> {
|
||||||
|
const jobData: ISyncUserQuotaJob = {
|
||||||
|
userId,
|
||||||
|
businessId,
|
||||||
|
wildduckUserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.quotaSyncQueue.add(QUOTA_SYNC_QUEUE.SYNC_USER_QUOTA_JOB, jobData, {
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
attempts: 3,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 2000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(`Scheduled user quota sync job for user: ${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a job to sync quota for a specific business
|
||||||
|
*/
|
||||||
|
async scheduleBusinessQuotaSync(businessId: string, businessName: string): Promise<void> {
|
||||||
|
const jobData: ISyncBusinessQuotaJob = {
|
||||||
|
businessId,
|
||||||
|
businessName,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.quotaSyncQueue.add(QUOTA_SYNC_QUEUE.SYNC_BUSINESS_QUOTA_JOB, jobData, {
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
attempts: 3,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 2000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(`Scheduled business quota sync job for business: ${businessName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a job for comprehensive quota sync
|
||||||
|
*/
|
||||||
|
async scheduleComprehensiveQuotaSync(): Promise<void> {
|
||||||
|
await this.quotaSyncQueue.add(
|
||||||
|
QUOTA_SYNC_QUEUE.SYNC_COMPREHENSIVE_QUOTA_JOB,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: false,
|
||||||
|
attempts: 3,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 2000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.logger.log("Scheduled comprehensive quota sync job");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following methods remain the same but without cron decorators
|
||||||
|
// They are now called by the queue processor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync quota usage for all users (called by processor)
|
||||||
*/
|
*/
|
||||||
@Cron(CronExpression.EVERY_2_HOURS)
|
|
||||||
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");
|
||||||
@@ -60,13 +169,13 @@ export class QuotaSyncService {
|
|||||||
this.logger.log(`Quota sync completed: ${successCount} success, ${errorCount} errors`);
|
this.logger.log(`Quota sync completed: ${successCount} success, ${errorCount} errors`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Failed to sync quota for users", `${error instanceof Error ? error.message : "Unknown error"}`);
|
this.logger.error("Failed to sync quota for users", `${error instanceof Error ? error.message : "Unknown error"}`);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync business quota usage every 4 hours
|
* Sync business quota usage for all businesses (called by processor)
|
||||||
*/
|
*/
|
||||||
@Cron(CronExpression.EVERY_4_HOURS)
|
|
||||||
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");
|
||||||
@@ -97,6 +206,7 @@ export class QuotaSyncService {
|
|||||||
this.logger.log(`Business quota sync completed: ${successCount} success, ${errorCount} errors`);
|
this.logger.log(`Business quota sync completed: ${successCount} success, ${errorCount} errors`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Failed to sync quota for businesses", `${error instanceof Error ? error.message : "Unknown error"}`);
|
this.logger.error("Failed to sync quota for businesses", `${error instanceof Error ? error.message : "Unknown error"}`);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export { CreateTemplateDto } from "./create-template.dto";
|
|
||||||
export { UpdateTemplateDto } from "./update-template.dto";
|
|
||||||
export { TemplateQueryDto } from "./template-query.dto";
|
|
||||||
export { TemplateParamDto } from "./template-param.dto";
|
|
||||||
@@ -1,44 +1,5 @@
|
|||||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
import { PartialType } from "@nestjs/swagger";
|
||||||
import { Type } from "class-transformer";
|
|
||||||
import { IsOptional, IsString, MaxLength, ValidateNested } from "class-validator";
|
|
||||||
|
|
||||||
import { TemplateMessage } from "../../../common/enums/message.enum";
|
import { CreateTemplateDto } from "./create-template.dto";
|
||||||
import { PersonalityDataType } from "../interfaces/structure.interface";
|
|
||||||
|
|
||||||
export class UpdateTemplateDto {
|
export class UpdateTemplateDto extends PartialType(CreateTemplateDto) {}
|
||||||
@IsOptional()
|
|
||||||
@IsString({ message: TemplateMessage.TEMPLATE_NAME_STRING })
|
|
||||||
@MaxLength(255, { message: TemplateMessage.TEMPLATE_NAME_MAX_LENGTH })
|
|
||||||
@ApiPropertyOptional({ description: "Template name", example: "Updated Corporate Email Template", maxLength: 255 })
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@ValidateNested({ message: TemplateMessage.TEMPLATE_STRUCTURE_VALID })
|
|
||||||
@Type(() => Object)
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: "Template structure containing header, content, and footer sections",
|
|
||||||
example: {
|
|
||||||
header: {
|
|
||||||
columnsCount: 1,
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
columnsCount: 2,
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
columnsCount: 1,
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
structure?: PersonalityDataType;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString({ message: TemplateMessage.TEMPLATE_RAW_HTML_STRING })
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: "Raw HTML content of the template",
|
|
||||||
example: "<html><body><h1>Updated Template Content</h1></body></html>",
|
|
||||||
})
|
|
||||||
rawHtml?: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { DomainAutomationService } from "../../domains/services/domain-automatio
|
|||||||
import { DomainsService } from "../../domains/services/domains.service";
|
import { DomainsService } from "../../domains/services/domains.service";
|
||||||
import { UpdateUserDto } from "../../mail-server/DTO";
|
import { UpdateUserDto } from "../../mail-server/DTO";
|
||||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||||
|
import { QuotaSyncService } from "../../quota-sync/services/quota-sync.service";
|
||||||
import { Template } from "../../templates/entities/template.entity";
|
import { Template } from "../../templates/entities/template.entity";
|
||||||
import { PasswordService } from "../../utils/services/password.service";
|
import { PasswordService } from "../../utils/services/password.service";
|
||||||
import { CreateEmailUserDto } from "../DTO/create-email-user.dto";
|
import { CreateEmailUserDto } from "../DTO/create-email-user.dto";
|
||||||
@@ -31,6 +32,7 @@ export class UsersService {
|
|||||||
private readonly mailServerService: MailServerService,
|
private readonly mailServerService: MailServerService,
|
||||||
private readonly domainsService: DomainsService,
|
private readonly domainsService: DomainsService,
|
||||||
private readonly domainAutomationService: DomainAutomationService,
|
private readonly domainAutomationService: DomainAutomationService,
|
||||||
|
private readonly quotaSyncService: QuotaSyncService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getUserEmailIdFromId(userId: string) {
|
async getUserEmailIdFromId(userId: string) {
|
||||||
@@ -169,6 +171,11 @@ export class UsersService {
|
|||||||
|
|
||||||
await this.em.flush();
|
await this.em.flush();
|
||||||
|
|
||||||
|
//sync quota
|
||||||
|
await this.quotaSyncService.scheduleUserQuotaSync(user.id, businessId, mailServerUser.id);
|
||||||
|
//sync business quota
|
||||||
|
await this.quotaSyncService.scheduleBusinessQuotaSync(businessId, business.name);
|
||||||
|
|
||||||
this.logger.log(`Email user created: ${emailAddress} for business ${businessId}`);
|
this.logger.log(`Email user created: ${emailAddress} for business ${businessId}`);
|
||||||
|
|
||||||
await this.domainAutomationService.createDefaultMailboxes(mailServerUser.id);
|
await this.domainAutomationService.createDefaultMailboxes(mailServerUser.id);
|
||||||
@@ -221,6 +228,11 @@ export class UsersService {
|
|||||||
|
|
||||||
await this.em.persistAndFlush(user);
|
await this.em.persistAndFlush(user);
|
||||||
|
|
||||||
|
//sync quota
|
||||||
|
await this.quotaSyncService.scheduleUserQuotaSync(user.id, businessId, user.wildduckUserId);
|
||||||
|
//sync business quota
|
||||||
|
await this.quotaSyncService.scheduleBusinessQuotaSync(businessId, user.business.name);
|
||||||
|
|
||||||
this.logger.log(`Email user deleted: ${user.emailAddress} for business ${businessId}`);
|
this.logger.log(`Email user deleted: ${user.emailAddress} for business ${businessId}`);
|
||||||
|
|
||||||
return { message: UserMessage.EMAIL_USER_DELETED_SUCCESSFULLY };
|
return { message: UserMessage.EMAIL_USER_DELETED_SUCCESSFULLY };
|
||||||
@@ -316,6 +328,14 @@ export class UsersService {
|
|||||||
|
|
||||||
await this.em.flush();
|
await this.em.flush();
|
||||||
|
|
||||||
|
//sync quota
|
||||||
|
if (user.wildduckUserId) {
|
||||||
|
await this.quotaSyncService.scheduleUserQuotaSync(user.id, businessId, user.wildduckUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
//sync business quota
|
||||||
|
await this.quotaSyncService.scheduleBusinessQuotaSync(businessId, user.business.name);
|
||||||
|
|
||||||
this.logger.log(`Email user updated: ${user.emailAddress} for business ${businessId}`);
|
this.logger.log(`Email user updated: ${user.emailAddress} for business ${businessId}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -372,6 +392,14 @@ export class UsersService {
|
|||||||
|
|
||||||
await this.em.persistAndFlush([user, user.business]);
|
await this.em.persistAndFlush([user, user.business]);
|
||||||
|
|
||||||
|
//sync quota
|
||||||
|
if (user.wildduckUserId) {
|
||||||
|
await this.quotaSyncService.scheduleUserQuotaSync(user.id, businessId, user.wildduckUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
//sync business quota
|
||||||
|
await this.quotaSyncService.scheduleBusinessQuotaSync(businessId, user.business.name);
|
||||||
|
|
||||||
this.logger.log(`Email user quota updated: ${user.emailAddress} to ${newQuota} bytes`);
|
this.logger.log(`Email user quota updated: ${user.emailAddress} to ${newQuota} bytes`);
|
||||||
|
|
||||||
return { message: UserMessage.EMAIL_USER_QUOTA_UPDATED_SUCCESSFULLY, user };
|
return { message: UserMessage.EMAIL_USER_QUOTA_UPDATED_SUCCESSFULLY, user };
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import { UsersController } from "./users.controller";
|
|||||||
import { BusinessesModule } from "../businesses/businesses.module";
|
import { BusinessesModule } from "../businesses/businesses.module";
|
||||||
import { DomainsModule } from "../domains/domains.module";
|
import { DomainsModule } from "../domains/domains.module";
|
||||||
import { MailServerModule } from "../mail-server/mail-server.module";
|
import { MailServerModule } from "../mail-server/mail-server.module";
|
||||||
|
import { QuotaSyncModule } from "../quota-sync/quota-sync.module";
|
||||||
import { UtilsModule } from "../utils/utils.module";
|
import { UtilsModule } from "../utils/utils.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [MikroOrmModule.forFeature([User, RefreshToken, Role]), UtilsModule, MailServerModule, DomainsModule, BusinessesModule],
|
imports: [MikroOrmModule.forFeature([User, RefreshToken, Role]), UtilsModule, MailServerModule, DomainsModule, BusinessesModule, QuotaSyncModule],
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
providers: [UsersService],
|
providers: [UsersService],
|
||||||
exports: [UsersService],
|
exports: [UsersService],
|
||||||
|
|||||||
Reference in New Issue
Block a user