Refactor backend folder structure (#4505)

* Refactor backend folder structure

Co-authored-by: Charles Bochet <charles@twenty.com>

* fix tests

* fix

* move yoga hooks

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Weiko
2024-03-15 18:37:09 +01:00
committed by GitHub
parent afb9b3e375
commit 2c09096edd
523 changed files with 1386 additions and 1856 deletions

View File

@ -0,0 +1,66 @@
import { plainToClass } from 'class-transformer';
import { CastToLogLevelArray } from 'src/engine/integrations/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();
});
});

View File

@ -0,0 +1,58 @@
import { plainToClass } from 'class-transformer';
import { CastToPositiveNumber } from 'src/engine/integrations/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);
});
});

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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,
});
};

View File

@ -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,
});
};

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

@ -0,0 +1,289 @@
import { LogLevel } from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import {
IsEnum,
IsOptional,
IsString,
IsUrl,
ValidateIf,
validateSync,
IsBoolean,
IsNumber,
IsDefined,
} from 'class-validator';
import { EmailDriver } from 'src/engine/integrations/email/interfaces/email.interface';
import { assert } from 'src/utils/assert';
import { CastToStringArray } from 'src/engine/integrations/environment/decorators/cast-to-string-array.decorator';
import { ExceptionHandlerDriver } from 'src/engine/integrations/exception-handler/interfaces';
import { StorageDriverType } from 'src/engine/integrations/file-storage/interfaces';
import { LoggerDriverType } from 'src/engine/integrations/logger/interfaces';
import { IsStrictlyLowerThan } from 'src/engine/integrations/environment/decorators/is-strictly-lower-than.decorator';
import { IsDuration } from './decorators/is-duration.decorator';
import { AwsRegion } from './interfaces/aws-region.interface';
import { IsAWSRegion } from './decorators/is-aws-region.decorator';
import { CastToBoolean } from './decorators/cast-to-boolean.decorator';
import { SupportDriver } from './interfaces/support.interface';
import { CastToPositiveNumber } from './decorators/cast-to-positive-number.decorator';
import { CastToLogLevelArray } from './decorators/cast-to-log-level-array.decorator';
export class EnvironmentVariables {
// Misc
@CastToBoolean()
@IsOptional()
@IsBoolean()
DEBUG_MODE: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
SIGN_IN_PREFILLED: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
IS_BILLING_ENABLED: boolean;
@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: number;
@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: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
TELEMETRY_ANONYMIZATION_ENABLED: boolean;
@CastToPositiveNumber()
@IsNumber()
@IsOptional()
PORT: number;
// Database
@IsDefined()
@IsUrl({
protocols: ['postgres'],
require_tld: false,
allow_underscores: true,
})
PG_DATABASE_URL: string;
// 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: string;
@IsString()
REFRESH_TOKEN_SECRET: string;
@IsDuration()
@IsOptional()
REFRESH_TOKEN_EXPIRES_IN: string;
@IsDuration()
@IsOptional()
REFRESH_TOKEN_COOL_DOWN: string;
@IsString()
LOGIN_TOKEN_SECRET: string;
@IsDuration()
@IsOptional()
LOGIN_TOKEN_EXPIRES_IN: string;
// Auth
@IsUrl({ require_tld: false })
@IsOptional()
FRONT_AUTH_CALLBACK_URL: string;
@CastToBoolean()
@IsOptional()
@IsBoolean()
AUTH_GOOGLE_ENABLED: boolean;
@IsString()
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
AUTH_GOOGLE_CLIENT_ID: string;
@IsString()
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
AUTH_GOOGLE_CLIENT_SECRET: string;
@IsUrl({ require_tld: false })
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
AUTH_GOOGLE_CALLBACK_URL: string;
// Storage
@IsEnum(StorageDriverType)
@IsOptional()
STORAGE_TYPE: StorageDriverType;
@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()
STORAGE_S3_ENDPOINT: string;
@IsString()
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.Local)
STORAGE_LOCAL_PATH: string;
// Support
@IsEnum(SupportDriver)
@IsOptional()
SUPPORT_DRIVER: SupportDriver;
@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;
@IsEnum(ExceptionHandlerDriver)
@IsOptional()
EXCEPTION_HANDLER_DRIVER: ExceptionHandlerDriver;
@CastToLogLevelArray()
@IsOptional()
LOG_LEVELS: LogLevel[];
@CastToStringArray()
@IsOptional()
DEMO_WORKSPACE_IDS: string[];
@ValidateIf(
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
)
@IsString()
SENTRY_DSN: string;
@IsDuration()
@IsOptional()
PASSWORD_RESET_TOKEN_EXPIRES_IN: 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()
IS_SIGN_UP_DISABLED: boolean;
@CastToPositiveNumber()
@IsOptional()
@IsNumber()
MUTATION_MAXIMUM_RECORD_AFFECTED: number;
REDIS_HOST: string;
REDIS_PORT: number;
API_TOKEN_EXPIRES_IN: string;
SHORT_TERM_TOKEN_EXPIRES_IN: string;
MESSAGING_PROVIDER_GMAIL_ENABLED: boolean;
MESSAGING_PROVIDER_GMAIL_CALLBACK_URL: string;
MESSAGE_QUEUE_TYPE: string;
EMAIL_FROM_ADDRESS: string;
EMAIL_SYSTEM_ADDRESS: string;
EMAIL_FROM_NAME: string;
EMAIL_DRIVER: EmailDriver;
EMAIL_SMTP_HOST: string;
EMAIL_SMTP_PORT: number;
EMAIL_SMTP_USER: string;
EMAIL_SMTP_PASSWORD: string;
OPENROUTER_API_KEY: string;
API_RATE_LIMITING_TTL: number;
API_RATE_LIMITING_LIMIT: number;
CACHE_STORAGE_TYPE: string;
CACHE_STORAGE_TTL: number;
CALENDAR_PROVIDER_GOOGLE_ENABLED: boolean;
AUTH_GOOGLE_APIS_CALLBACK_URL: string;
}
export const validate = (config: Record<string, unknown>) => {
const validatedConfig = plainToClass(EnvironmentVariables, config);
const errors = validateSync(validatedConfig);
assert(!errors.length, errors.toString());
return validatedConfig;
};

View File

@ -0,0 +1,77 @@
import { EmailDriver } from 'src/engine/integrations/email/interfaces/email.interface';
import { SupportDriver } from 'src/engine/integrations/environment/interfaces/support.interface';
import { ExceptionHandlerDriver } from 'src/engine/integrations/exception-handler/interfaces';
import { StorageDriverType } from 'src/engine/integrations/file-storage/interfaces';
import { LoggerDriverType } from 'src/engine/integrations/logger/interfaces';
import { MessageQueueDriverType } from 'src/engine/integrations/message-queue/interfaces';
import { EnvironmentVariables } from 'src/engine/integrations/environment/environment-variables';
const EnvironmentDefault = new EnvironmentVariables();
EnvironmentDefault.DEBUG_MODE = false;
EnvironmentDefault.SIGN_IN_PREFILLED = false;
EnvironmentDefault.IS_BILLING_ENABLED = false;
EnvironmentDefault.BILLING_PLAN_REQUIRED_LINK = '';
EnvironmentDefault.BILLING_STRIPE_BASE_PLAN_PRODUCT_ID = '';
EnvironmentDefault.BILLING_FREE_TRIAL_DURATION_IN_DAYS = 7;
EnvironmentDefault.BILLING_STRIPE_API_KEY = '';
EnvironmentDefault.BILLING_STRIPE_WEBHOOK_SECRET = '';
EnvironmentDefault.TELEMETRY_ENABLED = true;
EnvironmentDefault.TELEMETRY_ANONYMIZATION_ENABLED = true;
EnvironmentDefault.PORT = 3000;
EnvironmentDefault.REDIS_HOST = '127.0.0.1';
EnvironmentDefault.REDIS_PORT = 6379;
EnvironmentDefault.PG_DATABASE_URL = '';
EnvironmentDefault.FRONT_BASE_URL = '';
EnvironmentDefault.SERVER_URL = '';
EnvironmentDefault.ACCESS_TOKEN_SECRET = 'random_string';
EnvironmentDefault.ACCESS_TOKEN_EXPIRES_IN = '30m';
EnvironmentDefault.REFRESH_TOKEN_SECRET = 'random_string';
EnvironmentDefault.REFRESH_TOKEN_EXPIRES_IN = '30m';
EnvironmentDefault.REFRESH_TOKEN_COOL_DOWN = '1m';
EnvironmentDefault.LOGIN_TOKEN_SECRET = 'random_string';
EnvironmentDefault.LOGIN_TOKEN_EXPIRES_IN = '30m';
EnvironmentDefault.API_TOKEN_EXPIRES_IN = '100y';
EnvironmentDefault.SHORT_TERM_TOKEN_EXPIRES_IN = '5m';
EnvironmentDefault.FRONT_AUTH_CALLBACK_URL = '';
EnvironmentDefault.MESSAGING_PROVIDER_GMAIL_ENABLED = false;
EnvironmentDefault.MESSAGING_PROVIDER_GMAIL_CALLBACK_URL = '';
EnvironmentDefault.AUTH_GOOGLE_ENABLED = false;
EnvironmentDefault.AUTH_GOOGLE_CLIENT_ID = '';
EnvironmentDefault.AUTH_GOOGLE_CLIENT_SECRET = '';
EnvironmentDefault.AUTH_GOOGLE_CALLBACK_URL = '';
EnvironmentDefault.STORAGE_TYPE = StorageDriverType.Local;
EnvironmentDefault.STORAGE_S3_REGION = 'aws-east-1';
EnvironmentDefault.STORAGE_S3_NAME = '';
EnvironmentDefault.STORAGE_S3_ENDPOINT = '';
EnvironmentDefault.STORAGE_LOCAL_PATH = '.local-storage';
EnvironmentDefault.MESSAGE_QUEUE_TYPE = MessageQueueDriverType.Sync;
EnvironmentDefault.EMAIL_FROM_ADDRESS = 'noreply@yourdomain.com';
EnvironmentDefault.EMAIL_SYSTEM_ADDRESS = 'system@yourdomain.com';
EnvironmentDefault.EMAIL_FROM_NAME = 'John from Twenty';
EnvironmentDefault.EMAIL_DRIVER = EmailDriver.Logger;
EnvironmentDefault.EMAIL_SMTP_HOST = '';
EnvironmentDefault.EMAIL_SMTP_PORT = 587;
EnvironmentDefault.EMAIL_SMTP_USER = '';
EnvironmentDefault.EMAIL_SMTP_PASSWORD = '';
EnvironmentDefault.SUPPORT_DRIVER = SupportDriver.None;
EnvironmentDefault.SUPPORT_FRONT_CHAT_ID = '';
EnvironmentDefault.SUPPORT_FRONT_HMAC_KEY = '';
EnvironmentDefault.LOGGER_DRIVER = LoggerDriverType.Console;
EnvironmentDefault.EXCEPTION_HANDLER_DRIVER = ExceptionHandlerDriver.Console;
EnvironmentDefault.LOG_LEVELS = ['log', 'error', 'warn'];
EnvironmentDefault.SENTRY_DSN = '';
EnvironmentDefault.DEMO_WORKSPACE_IDS = [];
EnvironmentDefault.OPENROUTER_API_KEY = '';
EnvironmentDefault.PASSWORD_RESET_TOKEN_EXPIRES_IN = '5m';
EnvironmentDefault.WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION = 30;
EnvironmentDefault.WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION = 60;
EnvironmentDefault.IS_SIGN_UP_DISABLED = false;
EnvironmentDefault.API_RATE_LIMITING_TTL = 100;
EnvironmentDefault.API_RATE_LIMITING_LIMIT = 500;
EnvironmentDefault.MUTATION_MAXIMUM_RECORD_AFFECTED = 100;
EnvironmentDefault.CACHE_STORAGE_TYPE = 'memory';
EnvironmentDefault.CACHE_STORAGE_TTL = 3600 * 24 * 7;
export { EnvironmentDefault };

View File

@ -0,0 +1,8 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder({
moduleName: 'Environment',
})
.setClassMethodName('forRoot')
.build();

View File

@ -0,0 +1,20 @@
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { EnvironmentService } from './environment.service';
import { ConfigurableModuleClass } from './environment.module-definition';
import { validate } from './environment-variables';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
expandVariables: true,
validate,
}),
],
providers: [EnvironmentService],
exports: [EnvironmentService],
})
export class EnvironmentModule extends ConfigurableModuleClass {}

View File

@ -0,0 +1,26 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { EnvironmentService } from './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();
});
});

View File

@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import { EnvironmentVariables } from 'src/engine/integrations/environment/environment-variables';
import { EnvironmentDefault } from 'src/engine/integrations/environment/environment.default';
@Injectable()
export class EnvironmentService {
constructor(private configService: ConfigService) {}
get<T extends keyof EnvironmentVariables>(key: T): EnvironmentVariables[T] {
return (
this.configService.get<EnvironmentVariables[T]>(key) ??
EnvironmentDefault[key]
);
}
getServerUrl(): string {
const url = this.configService.get<string>('SERVER_URL')!;
if (url?.endsWith('/')) {
return url.substring(0, url.length - 1);
}
return url;
}
getBaseUrl(request: Request): string {
return (
this.getServerUrl() || `${request.protocol}://${request.get('host')}`
);
}
getFrontAuthCallbackUrl(): string {
return (
this.configService.get<string>('FRONT_AUTH_CALLBACK_URL') ??
this.get('FRONT_BASE_URL') + '/verify'
);
}
// TODO: check because it isn't called
getLoggerIsBufferEnabled(): boolean | undefined {
return this.configService.get<boolean>('LOGGER_IS_BUFFER_ENABLED') ?? true;
}
}

View File

@ -0,0 +1 @@
export type AwsRegion = `${string}-${string}-${number}`;

View File

@ -0,0 +1,4 @@
export enum SupportDriver {
None = 'none',
Front = 'front',
}