6658 workflows add a first twenty piece email sender (#6965)
This commit is contained in:
@ -0,0 +1,66 @@
|
||||
import { plainToClass } from 'class-transformer';
|
||||
|
||||
import { CastToLogLevelArray } from 'src/engine/core-modules/environment/decorators/cast-to-log-level-array.decorator';
|
||||
|
||||
class TestClass {
|
||||
@CastToLogLevelArray()
|
||||
logLevels?: any;
|
||||
}
|
||||
|
||||
describe('CastToLogLevelArray Decorator', () => {
|
||||
it('should cast "log" to ["log"]', () => {
|
||||
const transformedClass = plainToClass(TestClass, { logLevels: 'log' });
|
||||
|
||||
expect(transformedClass.logLevels).toStrictEqual(['log']);
|
||||
});
|
||||
|
||||
it('should cast "error" to ["error"]', () => {
|
||||
const transformedClass = plainToClass(TestClass, { logLevels: 'error' });
|
||||
|
||||
expect(transformedClass.logLevels).toStrictEqual(['error']);
|
||||
});
|
||||
|
||||
it('should cast "warn" to ["warn"]', () => {
|
||||
const transformedClass = plainToClass(TestClass, { logLevels: 'warn' });
|
||||
|
||||
expect(transformedClass.logLevels).toStrictEqual(['warn']);
|
||||
});
|
||||
|
||||
it('should cast "debug" to ["debug"]', () => {
|
||||
const transformedClass = plainToClass(TestClass, { logLevels: 'debug' });
|
||||
|
||||
expect(transformedClass.logLevels).toStrictEqual(['debug']);
|
||||
});
|
||||
|
||||
it('should cast "verbose" to ["verbose"]', () => {
|
||||
const transformedClass = plainToClass(TestClass, { logLevels: 'verbose' });
|
||||
|
||||
expect(transformedClass.logLevels).toStrictEqual(['verbose']);
|
||||
});
|
||||
|
||||
it('should cast "verbose,error,warn" to ["verbose", "error", "warn"]', () => {
|
||||
const transformedClass = plainToClass(TestClass, {
|
||||
logLevels: 'verbose,error,warn',
|
||||
});
|
||||
|
||||
expect(transformedClass.logLevels).toStrictEqual([
|
||||
'verbose',
|
||||
'error',
|
||||
'warn',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should cast "toto" to undefined', () => {
|
||||
const transformedClass = plainToClass(TestClass, { logLevels: 'toto' });
|
||||
|
||||
expect(transformedClass.logLevels).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should cast "verbose,error,toto" to undefined', () => {
|
||||
const transformedClass = plainToClass(TestClass, {
|
||||
logLevels: 'verbose,error,toto',
|
||||
});
|
||||
|
||||
expect(transformedClass.logLevels).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,58 @@
|
||||
import { plainToClass } from 'class-transformer';
|
||||
|
||||
import { CastToPositiveNumber } from 'src/engine/core-modules/environment/decorators/cast-to-positive-number.decorator';
|
||||
|
||||
class TestClass {
|
||||
@CastToPositiveNumber()
|
||||
numberProperty?: any;
|
||||
}
|
||||
|
||||
describe('CastToPositiveNumber Decorator', () => {
|
||||
it('should cast number to number', () => {
|
||||
const transformedClass = plainToClass(TestClass, { numberProperty: 123 });
|
||||
|
||||
expect(transformedClass.numberProperty).toBe(123);
|
||||
});
|
||||
|
||||
it('should cast string to number', () => {
|
||||
const transformedClass = plainToClass(TestClass, { numberProperty: '123' });
|
||||
|
||||
expect(transformedClass.numberProperty).toBe(123);
|
||||
});
|
||||
|
||||
it('should cast null to undefined', () => {
|
||||
const transformedClass = plainToClass(TestClass, { numberProperty: null });
|
||||
|
||||
expect(transformedClass.numberProperty).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should cast negative number to undefined', () => {
|
||||
const transformedClass = plainToClass(TestClass, { numberProperty: -12 });
|
||||
|
||||
expect(transformedClass.numberProperty).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should cast undefined to undefined', () => {
|
||||
const transformedClass = plainToClass(TestClass, {
|
||||
numberProperty: undefined,
|
||||
});
|
||||
|
||||
expect(transformedClass.numberProperty).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should cast NaN string to undefined', () => {
|
||||
const transformedClass = plainToClass(TestClass, {
|
||||
numberProperty: 'toto',
|
||||
});
|
||||
|
||||
expect(transformedClass.numberProperty).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should cast a negative string to undefined', () => {
|
||||
const transformedClass = plainToClass(TestClass, {
|
||||
numberProperty: '-123',
|
||||
});
|
||||
|
||||
expect(transformedClass.numberProperty).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export const CastToBoolean = () =>
|
||||
Transform(({ value }: { value: string }) => toBoolean(value));
|
||||
|
||||
const toBoolean = (value: any) => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
if (['true', 'on', 'yes', '1'].includes(value.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export const CastToLogLevelArray = () =>
|
||||
Transform(({ value }: { value: string }) => toLogLevelArray(value));
|
||||
|
||||
const toLogLevelArray = (value: any) => {
|
||||
if (typeof value === 'string') {
|
||||
const rawLogLevels = value.split(',').map((level) => level.trim());
|
||||
const isInvalid = rawLogLevels.some(
|
||||
(level) => !['log', 'error', 'warn', 'debug', 'verbose'].includes(level),
|
||||
);
|
||||
|
||||
if (!isInvalid) {
|
||||
return rawLogLevels;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export const CastToPositiveNumber = () =>
|
||||
Transform(({ value }: { value: string }) => toNumber(value));
|
||||
|
||||
const toNumber = (value: any) => {
|
||||
if (typeof value === 'number') {
|
||||
return value >= 0 ? value : undefined;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return isNaN(+value) ? undefined : toNumber(+value);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export const CastToStringArray = () =>
|
||||
Transform(({ value }: { value: string }) => toStringArray(value));
|
||||
|
||||
const toStringArray = (value: any) => {
|
||||
if (typeof value === 'string') {
|
||||
return value.split(',').map((item) => item.trim());
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
|
||||
@ValidatorConstraint({ async: true })
|
||||
export class IsAWSRegionConstraint implements ValidatorConstraintInterface {
|
||||
validate(region: string) {
|
||||
const regex = /^[a-z]{2}-[a-z]+-\d{1}$/;
|
||||
|
||||
return regex.test(region); // Returns true if region matches regex
|
||||
}
|
||||
}
|
||||
|
||||
export const IsAWSRegion =
|
||||
(validationOptions?: ValidationOptions) =>
|
||||
(object: object, propertyName: string) => {
|
||||
registerDecorator({
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
constraints: [],
|
||||
validator: IsAWSRegionConstraint,
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
|
||||
@ValidatorConstraint({ async: true })
|
||||
export class IsDurationConstraint implements ValidatorConstraintInterface {
|
||||
validate(duration: string) {
|
||||
const regex =
|
||||
/^-?[0-9]+(.[0-9]+)?(m(illiseconds?)?|s(econds?)?|h((ou)?rs?)?|d(ays?)?|w(eeks?)?|M(onths?)?|y(ears?)?)?$/;
|
||||
|
||||
return regex.test(duration); // Returns true if duration matches regex
|
||||
}
|
||||
}
|
||||
|
||||
export const IsDuration =
|
||||
(validationOptions?: ValidationOptions) =>
|
||||
(object: object, propertyName: string) => {
|
||||
registerDecorator({
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
options: validationOptions,
|
||||
constraints: [],
|
||||
validator: IsDurationConstraint,
|
||||
});
|
||||
};
|
||||
@ -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
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,446 @@
|
||||
import { LogLevel } from '@nestjs/common';
|
||||
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDefined,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
Max,
|
||||
Min,
|
||||
ValidateIf,
|
||||
validateSync,
|
||||
} from 'class-validator';
|
||||
|
||||
import { EmailDriver } from 'src/engine/core-modules/email/interfaces/email.interface';
|
||||
import { NodeEnvironment } from 'src/engine/core-modules/environment/interfaces/node-environment.interface';
|
||||
import { LLMChatModelDriver } from 'src/engine/core-modules/llm-chat-model/interfaces/llm-chat-model.interface';
|
||||
import { LLMTracingDriver } from 'src/engine/core-modules/llm-tracing/interfaces/llm-tracing.interface';
|
||||
import { AwsRegion } from 'src/engine/core-modules/environment/interfaces/aws-region.interface';
|
||||
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
|
||||
|
||||
import { IsDuration } from 'src/engine/core-modules/environment/decorators/is-duration.decorator';
|
||||
import { IsAWSRegion } from 'src/engine/core-modules/environment/decorators/is-aws-region.decorator';
|
||||
import { CastToPositiveNumber } from 'src/engine/core-modules/environment/decorators/cast-to-positive-number.decorator';
|
||||
import { CastToLogLevelArray } from 'src/engine/core-modules/environment/decorators/cast-to-log-level-array.decorator';
|
||||
import { CastToBoolean } from 'src/engine/core-modules/environment/decorators/cast-to-boolean.decorator';
|
||||
import { CacheStorageType } from 'src/engine/core-modules/cache-storage/types/cache-storage-type.enum';
|
||||
import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
|
||||
import { CastToStringArray } from 'src/engine/core-modules/environment/decorators/cast-to-string-array.decorator';
|
||||
import { IsStrictlyLowerThan } from 'src/engine/core-modules/environment/decorators/is-strictly-lower-than.decorator';
|
||||
import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces';
|
||||
import { StorageDriverType } from 'src/engine/core-modules/file-storage/interfaces';
|
||||
import { LoggerDriverType } from 'src/engine/core-modules/logger/interfaces';
|
||||
import { MessageQueueDriverType } from 'src/engine/core-modules/message-queue/interfaces';
|
||||
import { ServerlessDriverType } from 'src/engine/core-modules/serverless/serverless.interface';
|
||||
import { assert } from 'src/utils/assert';
|
||||
|
||||
export class EnvironmentVariables {
|
||||
// Misc
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
DEBUG_MODE = false;
|
||||
|
||||
@IsEnum(NodeEnvironment)
|
||||
@IsString()
|
||||
NODE_ENV: NodeEnvironment = NodeEnvironment.development;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(65535)
|
||||
DEBUG_PORT = 9000;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
IS_BILLING_ENABLED = false;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.IS_BILLING_ENABLED === true)
|
||||
BILLING_PLAN_REQUIRED_LINK: string;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.IS_BILLING_ENABLED === true)
|
||||
BILLING_STRIPE_BASE_PLAN_PRODUCT_ID: string;
|
||||
|
||||
@IsNumber()
|
||||
@CastToPositiveNumber()
|
||||
@IsOptional()
|
||||
@ValidateIf((env) => env.IS_BILLING_ENABLED === true)
|
||||
BILLING_FREE_TRIAL_DURATION_IN_DAYS = 7;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.IS_BILLING_ENABLED === true)
|
||||
BILLING_STRIPE_API_KEY: string;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.IS_BILLING_ENABLED === true)
|
||||
BILLING_STRIPE_WEBHOOK_SECRET: string;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
TELEMETRY_ENABLED = true;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
PORT = 3000;
|
||||
|
||||
// Database
|
||||
@IsDefined()
|
||||
@IsUrl({
|
||||
protocols: ['postgres'],
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
PG_DATABASE_URL: string;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
PG_SSL_ALLOW_SELF_SIGNED = false;
|
||||
|
||||
// Frontend URL
|
||||
@IsUrl({ require_tld: false })
|
||||
FRONT_BASE_URL: string;
|
||||
|
||||
// Server URL
|
||||
@IsUrl({ require_tld: false })
|
||||
@IsOptional()
|
||||
SERVER_URL: string;
|
||||
|
||||
// Json Web Token
|
||||
@IsString()
|
||||
ACCESS_TOKEN_SECRET: string;
|
||||
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
ACCESS_TOKEN_EXPIRES_IN = '30m';
|
||||
|
||||
@IsString()
|
||||
REFRESH_TOKEN_SECRET: string;
|
||||
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
REFRESH_TOKEN_EXPIRES_IN = '60d';
|
||||
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
REFRESH_TOKEN_COOL_DOWN = '1m';
|
||||
|
||||
@IsString()
|
||||
LOGIN_TOKEN_SECRET = '30m';
|
||||
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
LOGIN_TOKEN_EXPIRES_IN = '15m';
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
FILE_TOKEN_SECRET = 'random_string';
|
||||
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
FILE_TOKEN_EXPIRES_IN = '1d';
|
||||
|
||||
// Auth
|
||||
@IsUrl({ require_tld: false })
|
||||
@IsOptional()
|
||||
FRONT_AUTH_CALLBACK_URL: string;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
AUTH_PASSWORD_ENABLED = true;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@ValidateIf((env) => env.AUTH_PASSWORD_ENABLED)
|
||||
SIGN_IN_PREFILLED = false;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
AUTH_MICROSOFT_ENABLED = false;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||
AUTH_MICROSOFT_CLIENT_ID: string;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||
AUTH_MICROSOFT_TENANT_ID: string;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||
AUTH_MICROSOFT_CLIENT_SECRET: string;
|
||||
|
||||
@IsUrl({ require_tld: false })
|
||||
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||
AUTH_MICROSOFT_CALLBACK_URL: string;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
AUTH_GOOGLE_ENABLED = false;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
|
||||
AUTH_GOOGLE_CLIENT_ID: string;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
|
||||
AUTH_GOOGLE_CLIENT_SECRET: string;
|
||||
|
||||
@IsUrl({ require_tld: false })
|
||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
|
||||
AUTH_GOOGLE_CALLBACK_URL: string;
|
||||
|
||||
// Custom Code Engine
|
||||
@IsEnum(ServerlessDriverType)
|
||||
@IsOptional()
|
||||
SERVERLESS_TYPE: ServerlessDriverType = ServerlessDriverType.Local;
|
||||
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@IsAWSRegion()
|
||||
SERVERLESS_LAMBDA_REGION: AwsRegion;
|
||||
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_ROLE: string;
|
||||
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID: string;
|
||||
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY: string;
|
||||
|
||||
// Storage
|
||||
@IsEnum(StorageDriverType)
|
||||
@IsOptional()
|
||||
STORAGE_TYPE: StorageDriverType = StorageDriverType.Local;
|
||||
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@IsAWSRegion()
|
||||
STORAGE_S3_REGION: AwsRegion;
|
||||
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@IsString()
|
||||
STORAGE_S3_NAME: string;
|
||||
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
STORAGE_S3_ENDPOINT: string;
|
||||
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
STORAGE_S3_ACCESS_KEY_ID: string;
|
||||
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
STORAGE_S3_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.Local)
|
||||
STORAGE_LOCAL_PATH = '.local-storage';
|
||||
|
||||
// Support
|
||||
@IsEnum(SupportDriver)
|
||||
@IsOptional()
|
||||
SUPPORT_DRIVER: SupportDriver = SupportDriver.None;
|
||||
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
||||
@IsString()
|
||||
SUPPORT_FRONT_CHAT_ID: string;
|
||||
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
||||
@IsString()
|
||||
SUPPORT_FRONT_HMAC_KEY: string;
|
||||
|
||||
@IsEnum(LoggerDriverType)
|
||||
@IsOptional()
|
||||
LOGGER_DRIVER: LoggerDriverType = LoggerDriverType.Console;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
LOGGER_IS_BUFFER_ENABLED = true;
|
||||
|
||||
@IsEnum(ExceptionHandlerDriver)
|
||||
@IsOptional()
|
||||
EXCEPTION_HANDLER_DRIVER: ExceptionHandlerDriver =
|
||||
ExceptionHandlerDriver.Console;
|
||||
|
||||
@CastToLogLevelArray()
|
||||
@IsOptional()
|
||||
LOG_LEVELS: LogLevel[] = ['log', 'error', 'warn'];
|
||||
|
||||
@CastToStringArray()
|
||||
@IsOptional()
|
||||
DEMO_WORKSPACE_IDS: string[] = [];
|
||||
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
)
|
||||
@IsString()
|
||||
SENTRY_DSN: string;
|
||||
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
)
|
||||
@IsString()
|
||||
SENTRY_FRONT_DSN: string;
|
||||
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
SENTRY_RELEASE: string;
|
||||
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
)
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
SENTRY_ENVIRONMENT: string;
|
||||
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
PASSWORD_RESET_TOKEN_EXPIRES_IN = '5m';
|
||||
|
||||
@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 = 30;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
@IsNumber()
|
||||
@ValidateIf((env) => env.WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION > 0)
|
||||
WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION = 60;
|
||||
|
||||
@CastToBoolean()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
IS_SIGN_UP_DISABLED = false;
|
||||
|
||||
@IsEnum(CaptchaDriverType)
|
||||
@IsOptional()
|
||||
CAPTCHA_DRIVER?: CaptchaDriverType;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
CAPTCHA_SITE_KEY?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
CAPTCHA_SECRET_KEY?: string;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
MUTATION_MAXIMUM_AFFECTED_RECORDS = 100;
|
||||
|
||||
REDIS_HOST = '127.0.0.1';
|
||||
|
||||
@CastToPositiveNumber()
|
||||
REDIS_PORT = 6379;
|
||||
|
||||
REDIS_USERNAME: string;
|
||||
|
||||
REDIS_PASSWORD: string;
|
||||
|
||||
API_TOKEN_EXPIRES_IN = '100y';
|
||||
|
||||
SHORT_TERM_TOKEN_EXPIRES_IN = '5m';
|
||||
|
||||
@CastToBoolean()
|
||||
MESSAGING_PROVIDER_GMAIL_ENABLED = false;
|
||||
|
||||
MESSAGE_QUEUE_TYPE: string = MessageQueueDriverType.Sync;
|
||||
|
||||
EMAIL_FROM_ADDRESS = 'noreply@yourdomain.com';
|
||||
|
||||
EMAIL_SYSTEM_ADDRESS = 'system@yourdomain.com';
|
||||
|
||||
EMAIL_FROM_NAME = 'Felix from Twenty';
|
||||
|
||||
EMAIL_DRIVER: EmailDriver = EmailDriver.Logger;
|
||||
|
||||
EMAIL_SMTP_HOST: string;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
EMAIL_SMTP_PORT = 587;
|
||||
|
||||
EMAIL_SMTP_USER: string;
|
||||
|
||||
EMAIL_SMTP_PASSWORD: string;
|
||||
|
||||
LLM_CHAT_MODEL_DRIVER: LLMChatModelDriver;
|
||||
|
||||
OPENAI_API_KEY: string;
|
||||
|
||||
LANGFUSE_SECRET_KEY: string;
|
||||
|
||||
LANGFUSE_PUBLIC_KEY: string;
|
||||
|
||||
LLM_TRACING_DRIVER: LLMTracingDriver = LLMTracingDriver.Console;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
API_RATE_LIMITING_TTL = 100;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
API_RATE_LIMITING_LIMIT = 500;
|
||||
|
||||
CACHE_STORAGE_TYPE: CacheStorageType = CacheStorageType.Memory;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
CACHE_STORAGE_TTL: number = 3600 * 24 * 7;
|
||||
|
||||
@CastToBoolean()
|
||||
CALENDAR_PROVIDER_GOOGLE_ENABLED = false;
|
||||
|
||||
AUTH_GOOGLE_APIS_CALLBACK_URL: string;
|
||||
|
||||
CHROME_EXTENSION_ID: string;
|
||||
|
||||
@CastToPositiveNumber()
|
||||
SERVERLESS_FUNCTION_EXEC_THROTTLE_LIMIT = 10;
|
||||
|
||||
// milliseconds
|
||||
@CastToPositiveNumber()
|
||||
SERVERLESS_FUNCTION_EXEC_THROTTLE_TTL = 1000;
|
||||
}
|
||||
|
||||
export const validate = (
|
||||
config: Record<string, unknown>,
|
||||
): EnvironmentVariables => {
|
||||
const validatedConfig = plainToClass(EnvironmentVariables, config);
|
||||
|
||||
const errors = validateSync(validatedConfig);
|
||||
|
||||
assert(!errors.length, errors.toString());
|
||||
|
||||
return validatedConfig;
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { ConfigurableModuleBuilder } from '@nestjs/common';
|
||||
|
||||
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
|
||||
new ConfigurableModuleBuilder({
|
||||
moduleName: 'Environment',
|
||||
})
|
||||
.setClassMethodName('forRoot')
|
||||
.build();
|
||||
@ -0,0 +1,20 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { ConfigurableModuleClass } from 'src/engine/core-modules/environment/environment.module-definition';
|
||||
import { validate } from 'src/engine/core-modules/environment/environment-variables';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
expandVariables: true,
|
||||
validate,
|
||||
}),
|
||||
],
|
||||
providers: [EnvironmentService],
|
||||
exports: [EnvironmentService],
|
||||
})
|
||||
export class EnvironmentModule extends ConfigurableModuleClass {}
|
||||
@ -0,0 +1,26 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
|
||||
describe('EnvironmentService', () => {
|
||||
let service: EnvironmentService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
EnvironmentService,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<EnvironmentService>(EnvironmentService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { EnvironmentVariables } from 'src/engine/core-modules/environment/environment-variables';
|
||||
|
||||
@Injectable()
|
||||
export class EnvironmentService {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
get<T extends keyof EnvironmentVariables>(key: T): EnvironmentVariables[T] {
|
||||
return this.configService.get<EnvironmentVariables[T]>(
|
||||
key,
|
||||
new EnvironmentVariables()[key],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export type AwsRegion = `${string}-${string}-${number}`;
|
||||
@ -0,0 +1,4 @@
|
||||
export enum NodeEnvironment {
|
||||
development = 'development',
|
||||
production = 'production',
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
export enum SupportDriver {
|
||||
None = 'none',
|
||||
Front = 'front',
|
||||
}
|
||||
Reference in New Issue
Block a user