2252 build a script to cleanup inactive workspaces (#3307)

* Add cron to message queue interfaces

* Add command to launch cron job

* Add command to stop cron job

* Update clean inactive workspaces job

* Add react-email

* WIP

* Fix import error

* Rename services

* Update logging

* Update email template

* Update email template

* Add Base Email template

* Move to proper place

* Remove test files

* Update logo

* Add email theme

* Revert "Remove test files"

This reverts commit fe062dd05166b95125cf99f2165cc20efb6c275a.

* Add email theme 2

* Revert "Revert "Remove test files""

This reverts commit 6c6471273ad765788f2eaf5a5614209edfb965ce.

* Revert "Revert "Revert "Remove test files"""

This reverts commit f851333c24e9cfe3f425c9cbbd1e079efce5c3dd.

* Revert "Revert "Revert "Revert "Remove test files""""

This reverts commit 7838e19e88e269026e24803f26cd52b467b4ef36.

* Fix theme

* Reorganize files

* Update clean inactive workspaces job

* Use env variable to define inactive days

* Remove FROM variable

* Use feature flag

* Fix cron command

* Remove useless variable

* Reorganize files

* Refactor some code

* Update email template

* Update email object

* Remove verbose log

* Code review returns

* Code review returns

* Simplify handle

* Code review returns

* Review

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
martmull
2024-01-13 12:03:41 +01:00
committed by GitHub
parent 03bf597301
commit 49a9a2c2be
27 changed files with 594 additions and 24 deletions

View File

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { SendMailOptions } from 'nodemailer';
@ -8,11 +8,11 @@ import { EmailSenderService } from 'src/integrations/email/email-sender.service'
@Injectable()
export class EmailSenderJob implements MessageQueueJob<SendMailOptions> {
private readonly logger = new Logger(EmailSenderJob.name);
constructor(private readonly emailSenderService: EmailSenderService) {}
async handle(data: SendMailOptions): Promise<void> {
process.stdout.write(`Sending email to ${data.to} ...`);
await this.emailSenderService.send(data);
console.log(' done!');
this.logger.log(`Email to ${data.to} sent`);
}
}

View File

@ -0,0 +1,32 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from 'class-validator';
export const IsStrictlyLowerThan = (
property: string,
validationOptions?: ValidationOptions,
) => {
return (object: object, propertyName: string) => {
registerDecorator({
name: 'isStrictlyLowerThan',
target: object.constructor,
propertyName: propertyName,
constraints: [property],
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return (
typeof value === 'number' &&
typeof relatedValue === 'number' &&
value < relatedValue
);
},
},
});
};
};

View File

@ -172,6 +172,27 @@ export class EnvironmentService {
);
}
getEmailFromAddress(): string {
return (
this.configService.get<string>('EMAIL_FROM_ADDRESS') ??
'noreply@yourdomain.com'
);
}
getEmailSystemAddress(): string {
return (
this.configService.get<string>('EMAIL_SYSTEM_ADDRESS') ??
'system@yourdomain.com'
);
}
getEmailFromName(): string {
return (
this.configService.get<string>('EMAIL_FROM_NAME') ??
'John from YourDomain'
);
}
getEmailDriver(): EmailDriver {
return (
this.configService.get<EmailDriver>('EMAIL_DRIVER') ?? EmailDriver.Logger
@ -245,6 +266,18 @@ export class EnvironmentService {
return this.configService.get<string | undefined>('OPENROUTER_API_KEY');
}
getInactiveDaysBeforeEmail(): number | undefined {
return this.configService.get<number | undefined>(
'WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION',
);
}
getInactiveDaysBeforeDelete(): number | undefined {
return this.configService.get<number | undefined>(
'WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION',
);
}
isSignUpDisabled(): boolean {
return this.configService.get<boolean>('IS_SIGN_UP_DISABLED') ?? false;
}

View File

@ -17,6 +17,7 @@ import { CastToStringArray } from 'src/integrations/environment/decorators/cast-
import { ExceptionHandlerDriver } from 'src/integrations/exception-handler/interfaces';
import { StorageDriverType } from 'src/integrations/file-storage/interfaces';
import { LoggerDriverType } from 'src/integrations/logger/interfaces';
import { IsStrictlyLowerThan } from 'src/integrations/environment/decorators/is-strictly-lower-than.decorator';
import { IsDuration } from './decorators/is-duration.decorator';
import { AwsRegion } from './interfaces/aws-region.interface';
@ -171,6 +172,21 @@ export class EnvironmentVariables {
@IsString()
SENTRY_DSN?: string;
@CastToPositiveNumber()
@IsNumber()
@ValidateIf((env) => env.WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION > 0)
@IsStrictlyLowerThan('WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION', {
message:
'"WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION" should be strictly lower that "WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION"',
})
@ValidateIf((env) => env.WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION > 0)
WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION: number;
@CastToPositiveNumber()
@IsNumber()
@ValidateIf((env) => env.WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION > 0)
WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION: number;
@CastToBoolean()
@IsOptional()
@IsBoolean()

View File

@ -1,9 +1,12 @@
import { LogLevel } from '@nestjs/common';
export enum LoggerDriverType {
Console = 'console',
}
export interface ConsoleDriverFactoryOptions {
type: LoggerDriverType.Console;
logLevels?: LogLevel[];
}
export type LoggerModuleOptions = ConsoleDriverFactoryOptions;

View File

@ -13,11 +13,13 @@ export const loggerModuleFactory = async (
environmentService: EnvironmentService,
): Promise<LoggerModuleOptions> => {
const driverType = environmentService.getLoggerDriverType();
const logLevels = environmentService.getLogLevels();
switch (driverType) {
case LoggerDriverType.Console: {
return {
type: LoggerDriverType.Console,
logLevels: logLevels,
};
}
default:

View File

@ -42,9 +42,16 @@ export class LoggerModule extends ConfigurableModuleClass {
return null;
}
return config?.type === LoggerDriverType.Console
? new ConsoleLogger()
: undefined;
const logLevels = config.logLevels ?? [];
const logger =
config?.type === LoggerDriverType.Console
? new ConsoleLogger()
: undefined;
logger?.setLogLevels(logLevels);
return logger;
},
inject: options.inject || [],
};

View File

@ -1,4 +1,4 @@
export interface MessageQueueJob<T extends MessageQueueJobData> {
export interface MessageQueueJob<T extends MessageQueueJobData | undefined> {
handle(data: T): Promise<void> | void;
}

View File

@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { HttpModule } from '@nestjs/axios';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GmailFullSyncJob } from 'src/workspace/messaging/jobs/gmail-full-sync.job';
import { CallWebhookJobsJob } from 'src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job';
@ -8,10 +9,14 @@ import { CallWebhookJob } from 'src/workspace/workspace-query-runner/jobs/call-w
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { CleanInactiveWorkspaceJob } from 'src/workspace/cron/clean-inactive-workspaces/clean-inactive-workspace.job';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FetchWorkspaceMessagesModule } from 'src/workspace/messaging/services/fetch-workspace-messages.module';
import { GmailPartialSyncJob } from 'src/workspace/messaging/jobs/gmail-partial-sync.job';
import { EmailSenderJob } from 'src/integrations/email/email-sender.job';
import { UserModule } from 'src/core/user/user.module';
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
@Module({
imports: [
@ -21,6 +26,10 @@ import { EmailSenderJob } from 'src/integrations/email/email-sender.job';
HttpModule,
TypeORMModule,
FetchWorkspaceMessagesModule,
UserModule,
EnvironmentModule,
TypeORMModule,
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
],
providers: [
{
@ -40,9 +49,10 @@ import { EmailSenderJob } from 'src/integrations/email/email-sender.job';
useClass: CallWebhookJob,
},
{
provide: EmailSenderJob.name,
useClass: EmailSenderJob,
provide: CleanInactiveWorkspaceJob.name,
useClass: CleanInactiveWorkspaceJob,
},
{ provide: EmailSenderJob.name, useClass: EmailSenderJob },
],
})
export class JobsModule {