Update enums to be all caps (#12372)
- Make custom domain public (remove from lab) - Use ALL_CAPS definition for enums
This commit is contained in:
@ -324,7 +324,7 @@ export class AuthService {
|
||||
name: 'Chrome Extension',
|
||||
redirectUrl:
|
||||
this.twentyConfigService.get('NODE_ENV') ===
|
||||
NodeEnvironment.development
|
||||
NodeEnvironment.DEVELOPMENT
|
||||
? authorizeAppInput.redirectUrl
|
||||
: `https://${this.twentyConfigService.get(
|
||||
'CHROME_EXTENSION_ID',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import { CAPTCHA_DRIVER } from 'src/engine/core-modules/captcha/constants/captcha-driver.constants';
|
||||
import { CaptchaService } from 'src/engine/core-modules/captcha/captcha.service';
|
||||
import { CAPTCHA_DRIVER } from 'src/engine/core-modules/captcha/constants/captcha-driver.constants';
|
||||
import { GoogleRecaptchaDriver } from 'src/engine/core-modules/captcha/drivers/google-recaptcha.driver';
|
||||
import { TurnstileDriver } from 'src/engine/core-modules/captcha/drivers/turnstile.driver';
|
||||
import {
|
||||
@ -23,9 +23,9 @@ export class CaptchaModule {
|
||||
}
|
||||
|
||||
switch (config.type) {
|
||||
case CaptchaDriverType.GoogleRecaptcha:
|
||||
case CaptchaDriverType.GOOGLE_RECAPTCHA:
|
||||
return new GoogleRecaptchaDriver(config.options);
|
||||
case CaptchaDriverType.Turnstile:
|
||||
case CaptchaDriverType.TURNSTILE:
|
||||
return new TurnstileDriver(config.options);
|
||||
default:
|
||||
return;
|
||||
|
||||
@ -2,8 +2,8 @@ import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum CaptchaDriverType {
|
||||
GoogleRecaptcha = 'google-recaptcha',
|
||||
Turnstile = 'turnstile',
|
||||
GOOGLE_RECAPTCHA = 'GOOGLE_RECAPTCHA',
|
||||
TURNSTILE = 'TURNSTILE',
|
||||
}
|
||||
|
||||
registerEnumType(CaptchaDriverType, {
|
||||
@ -16,12 +16,12 @@ export type CaptchaDriverOptions = {
|
||||
};
|
||||
|
||||
export interface GoogleRecaptchaDriverFactoryOptions {
|
||||
type: CaptchaDriverType.GoogleRecaptcha;
|
||||
type: CaptchaDriverType.GOOGLE_RECAPTCHA;
|
||||
options: CaptchaDriverOptions;
|
||||
}
|
||||
|
||||
export interface TurnstileDriverFactoryOptions {
|
||||
type: CaptchaDriverType.Turnstile;
|
||||
type: CaptchaDriverType.TURNSTILE;
|
||||
options: CaptchaDriverOptions;
|
||||
}
|
||||
|
||||
|
||||
@ -61,13 +61,13 @@ describe('ClientConfigService', () => {
|
||||
IS_MULTIWORKSPACE_ENABLED: true,
|
||||
IS_EMAIL_VERIFICATION_REQUIRED: true,
|
||||
DEFAULT_SUBDOMAIN: 'app',
|
||||
NODE_ENV: NodeEnvironment.development,
|
||||
SUPPORT_DRIVER: SupportDriver.Front,
|
||||
NODE_ENV: NodeEnvironment.DEVELOPMENT,
|
||||
SUPPORT_DRIVER: SupportDriver.FRONT,
|
||||
SUPPORT_FRONT_CHAT_ID: 'chat-123',
|
||||
SENTRY_ENVIRONMENT: 'development',
|
||||
APP_VERSION: '1.0.0',
|
||||
SENTRY_FRONT_DSN: 'https://sentry.example.com',
|
||||
CAPTCHA_DRIVER: CaptchaDriverType.GoogleRecaptcha,
|
||||
CAPTCHA_DRIVER: CaptchaDriverType.GOOGLE_RECAPTCHA,
|
||||
CAPTCHA_SITE_KEY: 'site-key-123',
|
||||
CHROME_EXTENSION_ID: 'extension-123',
|
||||
MUTATION_MAXIMUM_AFFECTED_RECORDS: 1000,
|
||||
@ -120,7 +120,7 @@ describe('ClientConfigService', () => {
|
||||
frontDomain: 'app.twenty.com',
|
||||
debugMode: true,
|
||||
support: {
|
||||
supportDriver: 'Front',
|
||||
supportDriver: 'FRONT',
|
||||
supportFrontChatId: 'chat-123',
|
||||
},
|
||||
sentry: {
|
||||
@ -129,7 +129,7 @@ describe('ClientConfigService', () => {
|
||||
dsn: 'https://sentry.example.com',
|
||||
},
|
||||
captcha: {
|
||||
provider: 'GoogleRecaptcha',
|
||||
provider: 'GOOGLE_RECAPTCHA',
|
||||
siteKey: 'site-key-123',
|
||||
},
|
||||
chromeExtensionId: 'extension-123',
|
||||
@ -152,7 +152,7 @@ describe('ClientConfigService', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'NODE_ENV') return NodeEnvironment.production;
|
||||
if (key === 'NODE_ENV') return NodeEnvironment.PRODUCTION;
|
||||
if (key === 'IS_BILLING_ENABLED') return false;
|
||||
|
||||
return undefined;
|
||||
@ -191,14 +191,14 @@ describe('ClientConfigService', () => {
|
||||
|
||||
const result = await service.getClientConfig();
|
||||
|
||||
expect(result.support.supportDriver).toBe(SupportDriver.None);
|
||||
expect(result.support.supportDriver).toBe(SupportDriver.NONE);
|
||||
});
|
||||
|
||||
it('should handle billing enabled with feature flags', async () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'NODE_ENV') return NodeEnvironment.production;
|
||||
if (key === 'NODE_ENV') return NodeEnvironment.PRODUCTION;
|
||||
if (key === 'IS_BILLING_ENABLED') return true;
|
||||
|
||||
return undefined;
|
||||
@ -209,44 +209,4 @@ describe('ClientConfigService', () => {
|
||||
expect(result.canManageFeatureFlags).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformEnum', () => {
|
||||
it('should transform enum by direct key match', () => {
|
||||
const result = (service as any).transformEnum(
|
||||
'GoogleRecaptcha',
|
||||
CaptchaDriverType,
|
||||
);
|
||||
|
||||
expect(result).toBe(CaptchaDriverType.GoogleRecaptcha);
|
||||
});
|
||||
|
||||
it('should transform enum by value match', () => {
|
||||
const result = (service as any).transformEnum(
|
||||
'google-recaptcha',
|
||||
CaptchaDriverType,
|
||||
);
|
||||
|
||||
expect(result).toBe('GoogleRecaptcha');
|
||||
});
|
||||
|
||||
it('should transform SupportDriver enum correctly', () => {
|
||||
const result = (service as any).transformEnum('front', SupportDriver);
|
||||
|
||||
expect(result).toBe('Front');
|
||||
});
|
||||
|
||||
it('should throw error for unknown enum value', () => {
|
||||
expect(() => {
|
||||
(service as any).transformEnum('unknown-value', CaptchaDriverType);
|
||||
}).toThrow(
|
||||
'Unknown enum value: unknown-value. Available keys: GoogleRecaptcha, Turnstile. Available values: google-recaptcha, turnstile',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle direct key match for SupportDriver', () => {
|
||||
const result = (service as any).transformEnum('Front', SupportDriver);
|
||||
|
||||
expect(result).toBe(SupportDriver.Front);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
|
||||
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
||||
import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/support.interface';
|
||||
|
||||
import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
|
||||
import { ClientConfig } from 'src/engine/core-modules/client-config/client-config.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const';
|
||||
@ -57,11 +56,9 @@ export class ClientConfigService {
|
||||
frontDomain: this.domainManagerService.getFrontUrl().hostname,
|
||||
debugMode:
|
||||
this.twentyConfigService.get('NODE_ENV') ===
|
||||
NodeEnvironment.development,
|
||||
NodeEnvironment.DEVELOPMENT,
|
||||
support: {
|
||||
supportDriver: supportDriver
|
||||
? this.transformEnum(supportDriver, SupportDriver)
|
||||
: SupportDriver.None,
|
||||
supportDriver: supportDriver ? supportDriver : SupportDriver.NONE,
|
||||
supportFrontChatId: this.twentyConfigService.get(
|
||||
'SUPPORT_FRONT_CHAT_ID',
|
||||
),
|
||||
@ -72,9 +69,7 @@ export class ClientConfigService {
|
||||
dsn: this.twentyConfigService.get('SENTRY_FRONT_DSN'),
|
||||
},
|
||||
captcha: {
|
||||
provider: captchaProvider
|
||||
? this.transformEnum(captchaProvider, CaptchaDriverType)
|
||||
: undefined,
|
||||
provider: captchaProvider ? captchaProvider : undefined,
|
||||
siteKey: this.twentyConfigService.get('CAPTCHA_SITE_KEY'),
|
||||
},
|
||||
chromeExtensionId: this.twentyConfigService.get('CHROME_EXTENSION_ID'),
|
||||
@ -89,7 +84,7 @@ export class ClientConfigService {
|
||||
analyticsEnabled: this.twentyConfigService.get('ANALYTICS_ENABLED'),
|
||||
canManageFeatureFlags:
|
||||
this.twentyConfigService.get('NODE_ENV') ===
|
||||
NodeEnvironment.development ||
|
||||
NodeEnvironment.DEVELOPMENT ||
|
||||
this.twentyConfigService.get('IS_BILLING_ENABLED'),
|
||||
publicFeatureFlags: PUBLIC_FEATURE_FLAGS,
|
||||
isMicrosoftMessagingEnabled: this.twentyConfigService.get(
|
||||
@ -111,34 +106,4 @@ export class ClientConfigService {
|
||||
|
||||
return clientConfig;
|
||||
}
|
||||
|
||||
// GraphQL enum values are in PascalCase, but the config values are in kebab-case
|
||||
// This function transforms the config values, the same way GraphQL does
|
||||
private transformEnum<T extends Record<string, string>>(
|
||||
value: string,
|
||||
enumObject: T,
|
||||
): T[keyof T] {
|
||||
const directMatch = Object.keys(enumObject).find(
|
||||
(key) => key === value,
|
||||
) as keyof T;
|
||||
|
||||
if (directMatch) {
|
||||
return enumObject[directMatch];
|
||||
}
|
||||
|
||||
const valueMatch = Object.entries(enumObject).find(
|
||||
([, enumValue]) => enumValue === value,
|
||||
);
|
||||
|
||||
if (valueMatch) {
|
||||
return valueMatch[0] as T[keyof T];
|
||||
}
|
||||
|
||||
const availableKeys = Object.keys(enumObject);
|
||||
const availableValues = Object.values(enumObject);
|
||||
|
||||
throw new Error(
|
||||
`Unknown enum value: ${value}. Available keys: ${availableKeys.join(', ')}. Available values: ${availableValues.join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ describe('EmailDriverFactory', () => {
|
||||
it('should return "logger" for logger driver', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue(EmailDriver.Logger);
|
||||
.mockReturnValue(EmailDriver.LOGGER);
|
||||
|
||||
const result = factory['buildConfigKey']();
|
||||
|
||||
@ -42,7 +42,7 @@ describe('EmailDriverFactory', () => {
|
||||
});
|
||||
|
||||
it('should return smtp config key for smtp driver', () => {
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue(EmailDriver.Smtp);
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue(EmailDriver.SMTP);
|
||||
jest
|
||||
.spyOn(factory as any, 'getConfigGroupHash')
|
||||
.mockReturnValue('smtp-hash-123');
|
||||
@ -66,7 +66,7 @@ describe('EmailDriverFactory', () => {
|
||||
it('should create logger driver', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue(EmailDriver.Logger);
|
||||
.mockReturnValue(EmailDriver.LOGGER);
|
||||
|
||||
const driver = factory['createDriver']();
|
||||
|
||||
@ -80,7 +80,7 @@ describe('EmailDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'EMAIL_DRIVER':
|
||||
return EmailDriver.Smtp;
|
||||
return EmailDriver.SMTP;
|
||||
case 'EMAIL_SMTP_HOST':
|
||||
return 'smtp.example.com';
|
||||
case 'EMAIL_SMTP_PORT':
|
||||
@ -108,7 +108,7 @@ describe('EmailDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'EMAIL_DRIVER':
|
||||
return EmailDriver.Smtp;
|
||||
return EmailDriver.SMTP;
|
||||
case 'EMAIL_SMTP_HOST':
|
||||
return undefined;
|
||||
case 'EMAIL_SMTP_PORT':
|
||||
@ -136,7 +136,7 @@ describe('EmailDriverFactory', () => {
|
||||
it('should return current driver for logger', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue(EmailDriver.Logger);
|
||||
.mockReturnValue(EmailDriver.LOGGER);
|
||||
|
||||
const driver = factory.getCurrentDriver();
|
||||
|
||||
@ -147,7 +147,7 @@ describe('EmailDriverFactory', () => {
|
||||
it('should reuse driver when config key unchanged', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue(EmailDriver.Logger);
|
||||
.mockReturnValue(EmailDriver.LOGGER);
|
||||
|
||||
const driver1 = factory.getCurrentDriver();
|
||||
const driver2 = factory.getCurrentDriver();
|
||||
@ -159,7 +159,7 @@ describe('EmailDriverFactory', () => {
|
||||
// First call with logger
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue(EmailDriver.Logger);
|
||||
.mockReturnValue(EmailDriver.LOGGER);
|
||||
|
||||
const driver1 = factory.getCurrentDriver();
|
||||
|
||||
@ -169,7 +169,7 @@ describe('EmailDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'EMAIL_DRIVER':
|
||||
return EmailDriver.Smtp;
|
||||
return EmailDriver.SMTP;
|
||||
case 'EMAIL_SMTP_HOST':
|
||||
return 'smtp.example.com';
|
||||
case 'EMAIL_SMTP_PORT':
|
||||
@ -203,7 +203,7 @@ describe('EmailDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'EMAIL_DRIVER':
|
||||
return EmailDriver.Smtp;
|
||||
return EmailDriver.SMTP;
|
||||
case 'EMAIL_SMTP_HOST':
|
||||
return 'smtp.example.com';
|
||||
case 'EMAIL_SMTP_PORT':
|
||||
|
||||
@ -18,11 +18,11 @@ export class EmailDriverFactory extends DriverFactoryBase<EmailDriverInterface>
|
||||
protected buildConfigKey(): string {
|
||||
const driver = this.twentyConfigService.get('EMAIL_DRIVER');
|
||||
|
||||
if (driver === EmailDriver.Logger) {
|
||||
if (driver === EmailDriver.LOGGER) {
|
||||
return 'logger';
|
||||
}
|
||||
|
||||
if (driver === EmailDriver.Smtp) {
|
||||
if (driver === EmailDriver.SMTP) {
|
||||
const emailConfigHash = this.getConfigGroupHash(
|
||||
ConfigVariablesGroup.EmailSettings,
|
||||
);
|
||||
@ -37,10 +37,10 @@ export class EmailDriverFactory extends DriverFactoryBase<EmailDriverInterface>
|
||||
const driver = this.twentyConfigService.get('EMAIL_DRIVER');
|
||||
|
||||
switch (driver) {
|
||||
case EmailDriver.Logger:
|
||||
case EmailDriver.LOGGER:
|
||||
return new LoggerDriver();
|
||||
|
||||
case EmailDriver.Smtp: {
|
||||
case EmailDriver.SMTP: {
|
||||
const host = this.twentyConfigService.get('EMAIL_SMTP_HOST');
|
||||
const port = this.twentyConfigService.get('EMAIL_SMTP_PORT');
|
||||
const user = this.twentyConfigService.get('EMAIL_SMTP_USER');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum EmailDriver {
|
||||
Logger = 'logger',
|
||||
Smtp = 'smtp',
|
||||
LOGGER = 'LOGGER',
|
||||
SMTP = 'SMTP',
|
||||
}
|
||||
|
||||
@ -19,21 +19,21 @@ export const exceptionHandlerModuleFactory = async (
|
||||
const driverType = twentyConfigService.get('EXCEPTION_HANDLER_DRIVER');
|
||||
|
||||
switch (driverType) {
|
||||
case ExceptionHandlerDriver.Console: {
|
||||
case ExceptionHandlerDriver.CONSOLE: {
|
||||
return {
|
||||
type: ExceptionHandlerDriver.Console,
|
||||
type: ExceptionHandlerDriver.CONSOLE,
|
||||
};
|
||||
}
|
||||
case ExceptionHandlerDriver.Sentry: {
|
||||
case ExceptionHandlerDriver.SENTRY: {
|
||||
return {
|
||||
type: ExceptionHandlerDriver.Sentry,
|
||||
type: ExceptionHandlerDriver.SENTRY,
|
||||
options: {
|
||||
environment: twentyConfigService.get('SENTRY_ENVIRONMENT'),
|
||||
release: twentyConfigService.get('APP_VERSION'),
|
||||
dsn: twentyConfigService.get('SENTRY_DSN') ?? '',
|
||||
serverInstance: adapterHost.httpAdapter?.getInstance(),
|
||||
debug:
|
||||
twentyConfigService.get('NODE_ENV') === NodeEnvironment.development,
|
||||
twentyConfigService.get('NODE_ENV') === NodeEnvironment.DEVELOPMENT,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ export class ExceptionHandlerModule extends ConfigurableModuleClass {
|
||||
const provider = {
|
||||
provide: EXCEPTION_HANDLER_DRIVER,
|
||||
useValue:
|
||||
options.type === ExceptionHandlerDriver.Console
|
||||
options.type === ExceptionHandlerDriver.CONSOLE
|
||||
? new ExceptionHandlerConsoleDriver()
|
||||
: new ExceptionHandlerSentryDriver(),
|
||||
};
|
||||
@ -45,7 +45,7 @@ export class ExceptionHandlerModule extends ConfigurableModuleClass {
|
||||
return null;
|
||||
}
|
||||
|
||||
return config.type === ExceptionHandlerDriver.Console
|
||||
return config.type === ExceptionHandlerDriver.CONSOLE
|
||||
? new ExceptionHandlerConsoleDriver()
|
||||
: new ExceptionHandlerSentryDriver();
|
||||
},
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
export enum ExceptionHandlerDriver {
|
||||
Sentry = 'sentry',
|
||||
Console = 'console',
|
||||
SENTRY = 'SENTRY',
|
||||
CONSOLE = 'CONSOLE',
|
||||
}
|
||||
|
||||
export interface ExceptionHandlerSentryDriverFactoryOptions {
|
||||
type: ExceptionHandlerDriver.Sentry;
|
||||
type: ExceptionHandlerDriver.SENTRY;
|
||||
options: {
|
||||
environment?: string;
|
||||
release?: string;
|
||||
@ -17,7 +17,7 @@ export interface ExceptionHandlerSentryDriverFactoryOptions {
|
||||
}
|
||||
|
||||
export interface ExceptionHandlerDriverFactoryOptions {
|
||||
type: ExceptionHandlerDriver.Console;
|
||||
type: ExceptionHandlerDriver.CONSOLE;
|
||||
}
|
||||
|
||||
export type ExceptionHandlerModuleOptions =
|
||||
|
||||
@ -7,16 +7,13 @@ type FeatureFlagMetadata = {
|
||||
};
|
||||
|
||||
export type PublicFeatureFlag = {
|
||||
key: Extract<
|
||||
FeatureFlagKey,
|
||||
FeatureFlagKey.IsWorkflowEnabled | FeatureFlagKey.IsCustomDomainEnabled
|
||||
>;
|
||||
key: Extract<FeatureFlagKey, FeatureFlagKey.IS_WORKFLOW_ENABLED>;
|
||||
metadata: FeatureFlagMetadata;
|
||||
};
|
||||
|
||||
export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
||||
{
|
||||
key: FeatureFlagKey.IsWorkflowEnabled,
|
||||
key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
metadata: {
|
||||
label: 'Workflows',
|
||||
description: 'Create custom workflows to automate your work.',
|
||||
@ -25,15 +22,9 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
||||
},
|
||||
...(process.env.CLOUDFLARE_API_KEY
|
||||
? [
|
||||
{
|
||||
key: FeatureFlagKey.IsCustomDomainEnabled as PublicFeatureFlag['key'],
|
||||
metadata: {
|
||||
label: 'Custom Domain',
|
||||
description: 'Customize your workspace URL with your own domain.',
|
||||
imagePath:
|
||||
'https://twenty.com/images/lab/is-custom-domain-enabled.png',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// Here you can add cloud only feature flags
|
||||
// },
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
export enum FeatureFlagKey {
|
||||
IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
||||
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
||||
IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED',
|
||||
IsCopilotEnabled = 'IS_COPILOT_ENABLED',
|
||||
IsWorkflowEnabled = 'IS_WORKFLOW_ENABLED',
|
||||
IsUniqueIndexesEnabled = 'IS_UNIQUE_INDEXES_ENABLED',
|
||||
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
||||
IsCustomDomainEnabled = 'IS_CUSTOM_DOMAIN_ENABLED',
|
||||
IsPermissionsV2Enabled = 'IS_PERMISSIONS_V2_ENABLED',
|
||||
IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
||||
IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
||||
IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
|
||||
IS_COPILOT_ENABLED = 'IS_COPILOT_ENABLED',
|
||||
IS_WORKFLOW_ENABLED = 'IS_WORKFLOW_ENABLED',
|
||||
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED',
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ describe('FeatureFlagService', () => {
|
||||
};
|
||||
|
||||
const workspaceId = 'workspace-id';
|
||||
const featureFlag = FeatureFlagKey.IsWorkflowEnabled;
|
||||
const featureFlag = FeatureFlagKey.IS_WORKFLOW_ENABLED;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
@ -121,13 +121,13 @@ describe('FeatureFlagService', () => {
|
||||
// Prepare
|
||||
mockWorkspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMap.mockResolvedValue(
|
||||
{
|
||||
[FeatureFlagKey.IsWorkflowEnabled]: true,
|
||||
[FeatureFlagKey.IsCopilotEnabled]: false,
|
||||
[FeatureFlagKey.IS_WORKFLOW_ENABLED]: true,
|
||||
[FeatureFlagKey.IS_COPILOT_ENABLED]: false,
|
||||
},
|
||||
);
|
||||
const mockFeatureFlags = [
|
||||
{ key: FeatureFlagKey.IsWorkflowEnabled, value: true },
|
||||
{ key: FeatureFlagKey.IsCopilotEnabled, value: false },
|
||||
{ key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true },
|
||||
{ key: FeatureFlagKey.IS_COPILOT_ENABLED, value: false },
|
||||
];
|
||||
|
||||
// Act
|
||||
@ -145,8 +145,8 @@ describe('FeatureFlagService', () => {
|
||||
it('should return a map of feature flags for a workspace', async () => {
|
||||
// Prepare
|
||||
const mockFeatureFlags = [
|
||||
{ key: FeatureFlagKey.IsWorkflowEnabled, value: true, workspaceId },
|
||||
{ key: FeatureFlagKey.IsCopilotEnabled, value: false, workspaceId },
|
||||
{ key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true, workspaceId },
|
||||
{ key: FeatureFlagKey.IS_COPILOT_ENABLED, value: false, workspaceId },
|
||||
];
|
||||
|
||||
mockFeatureFlagRepository.find.mockResolvedValue(mockFeatureFlags);
|
||||
@ -156,8 +156,8 @@ describe('FeatureFlagService', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
[FeatureFlagKey.IsWorkflowEnabled]: true,
|
||||
[FeatureFlagKey.IsCopilotEnabled]: false,
|
||||
[FeatureFlagKey.IS_WORKFLOW_ENABLED]: true,
|
||||
[FeatureFlagKey.IS_COPILOT_ENABLED]: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -166,8 +166,8 @@ describe('FeatureFlagService', () => {
|
||||
it('should enable multiple feature flags for a workspace', async () => {
|
||||
// Prepare
|
||||
const keys = [
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
FeatureFlagKey.IsCopilotEnabled,
|
||||
FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
FeatureFlagKey.IS_COPILOT_ENABLED,
|
||||
];
|
||||
|
||||
mockFeatureFlagRepository.upsert.mockResolvedValue({});
|
||||
@ -212,7 +212,6 @@ describe('FeatureFlagService', () => {
|
||||
// Assert
|
||||
expect(result).toEqual(mockFeatureFlag);
|
||||
expect(mockFeatureFlagRepository.save).toHaveBeenCalledWith({
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
key: FeatureFlagKey[featureFlag],
|
||||
value,
|
||||
workspaceId,
|
||||
|
||||
@ -99,12 +99,9 @@ export class FeatureFlagService {
|
||||
),
|
||||
);
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
const featureFlagKey = FeatureFlagKey[featureFlag];
|
||||
|
||||
if (shouldBePublic) {
|
||||
publicFeatureFlagValidator.assertIsPublicFeatureFlag(
|
||||
featureFlagKey,
|
||||
featureFlag,
|
||||
new FeatureFlagException(
|
||||
'Invalid feature flag key, flag is not public',
|
||||
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
|
||||
@ -114,7 +111,7 @@ export class FeatureFlagService {
|
||||
|
||||
const existingFeatureFlag = await this.featureFlagRepository.findOne({
|
||||
where: {
|
||||
key: featureFlagKey,
|
||||
key: featureFlag,
|
||||
workspaceId: workspaceId,
|
||||
},
|
||||
});
|
||||
@ -125,7 +122,7 @@ export class FeatureFlagService {
|
||||
value,
|
||||
}
|
||||
: {
|
||||
key: featureFlagKey,
|
||||
key: featureFlag,
|
||||
value,
|
||||
workspaceId: workspaceId,
|
||||
};
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate';
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
describe('featureFlagValidator', () => {
|
||||
describe('assertIsFeatureFlagKey', () => {
|
||||
it('should not throw error if featureFlagKey is valid', () => {
|
||||
expect(() =>
|
||||
featureFlagValidator.assertIsFeatureFlagKey(
|
||||
'IsWorkflowEnabled',
|
||||
'IS_WORKFLOW_ENABLED',
|
||||
new CustomException('Error', 'Error'),
|
||||
),
|
||||
).not.toThrow();
|
||||
|
||||
@ -37,7 +37,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -55,7 +55,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
it('should build config key for S3 storage', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue(StorageDriverType.S3);
|
||||
.mockReturnValue(StorageDriverType.S_3);
|
||||
jest
|
||||
.spyOn(factory as any, 'getConfigGroupHash')
|
||||
.mockReturnValue('s3-hash-123');
|
||||
@ -84,7 +84,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -102,7 +102,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'STORAGE_TYPE':
|
||||
return StorageDriverType.S3;
|
||||
return StorageDriverType.S_3;
|
||||
case 'STORAGE_S3_NAME':
|
||||
return 'test-bucket';
|
||||
case 'STORAGE_S3_ENDPOINT':
|
||||
@ -130,7 +130,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'STORAGE_TYPE':
|
||||
return StorageDriverType.S3;
|
||||
return StorageDriverType.S_3;
|
||||
case 'STORAGE_S3_NAME':
|
||||
return 'test-bucket';
|
||||
case 'STORAGE_S3_ENDPOINT':
|
||||
@ -168,7 +168,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -186,7 +186,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -203,7 +203,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return '/tmp/storage1';
|
||||
|
||||
return undefined;
|
||||
@ -215,7 +215,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return '/tmp/storage2';
|
||||
|
||||
return undefined;
|
||||
@ -233,7 +233,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return '/tmp/storage';
|
||||
|
||||
return undefined;
|
||||
@ -247,7 +247,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'STORAGE_TYPE':
|
||||
return StorageDriverType.S3;
|
||||
return StorageDriverType.S_3;
|
||||
case 'STORAGE_S3_NAME':
|
||||
return 'test-bucket';
|
||||
case 'STORAGE_S3_ENDPOINT':
|
||||
@ -285,7 +285,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
|
||||
@ -21,13 +21,13 @@ export class FileStorageDriverFactory extends DriverFactoryBase<StorageDriver> {
|
||||
protected buildConfigKey(): string {
|
||||
const storageType = this.twentyConfigService.get('STORAGE_TYPE');
|
||||
|
||||
if (storageType === StorageDriverType.Local) {
|
||||
if (storageType === StorageDriverType.LOCAL) {
|
||||
const storagePath = this.twentyConfigService.get('STORAGE_LOCAL_PATH');
|
||||
|
||||
return `local|${storagePath}`;
|
||||
}
|
||||
|
||||
if (storageType === StorageDriverType.S3) {
|
||||
if (storageType === StorageDriverType.S_3) {
|
||||
const storageConfigHash = this.getConfigGroupHash(
|
||||
ConfigVariablesGroup.StorageConfig,
|
||||
);
|
||||
@ -42,7 +42,7 @@ export class FileStorageDriverFactory extends DriverFactoryBase<StorageDriver> {
|
||||
const storageType = this.twentyConfigService.get('STORAGE_TYPE');
|
||||
|
||||
switch (storageType) {
|
||||
case StorageDriverType.Local: {
|
||||
case StorageDriverType.LOCAL: {
|
||||
const storagePath = this.twentyConfigService.get('STORAGE_LOCAL_PATH');
|
||||
|
||||
return new LocalDriver({
|
||||
@ -50,7 +50,7 @@ export class FileStorageDriverFactory extends DriverFactoryBase<StorageDriver> {
|
||||
});
|
||||
}
|
||||
|
||||
case StorageDriverType.S3: {
|
||||
case StorageDriverType.S_3: {
|
||||
const bucketName = this.twentyConfigService.get('STORAGE_S3_NAME');
|
||||
const endpoint = this.twentyConfigService.get('STORAGE_S3_ENDPOINT');
|
||||
const region = this.twentyConfigService.get('STORAGE_S3_REGION');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum StorageDriverType {
|
||||
S3 = 's3',
|
||||
Local = 'local',
|
||||
S_3 = 'S_3',
|
||||
LOCAL = 'LOCAL',
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ModuleMetadata, FactoryProvider } from '@nestjs/common';
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
export enum LLMChatModelDriver {
|
||||
OpenAI = 'openai',
|
||||
OPENAI = 'OPENAI',
|
||||
}
|
||||
|
||||
export interface LLMChatModelModuleOptions {
|
||||
|
||||
@ -8,8 +8,8 @@ export const llmChatModelModuleFactory = (
|
||||
const driver = twentyConfigService.get('LLM_CHAT_MODEL_DRIVER');
|
||||
|
||||
switch (driver) {
|
||||
case LLMChatModelDriver.OpenAI: {
|
||||
return { type: LLMChatModelDriver.OpenAI };
|
||||
case LLMChatModelDriver.OPENAI: {
|
||||
return { type: LLMChatModelDriver.OPENAI };
|
||||
}
|
||||
default:
|
||||
// `No LLM chat model driver (${driver})`);
|
||||
|
||||
@ -5,8 +5,8 @@ import {
|
||||
LLMChatModelModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/llm-chat-model/interfaces/llm-chat-model.interface';
|
||||
|
||||
import { LLM_CHAT_MODEL_DRIVER } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.constants';
|
||||
import { OpenAIDriver } from 'src/engine/core-modules/llm-chat-model/drivers/openai.driver';
|
||||
import { LLM_CHAT_MODEL_DRIVER } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.constants';
|
||||
import { LLMChatModelService } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.service';
|
||||
|
||||
@Global()
|
||||
@ -19,7 +19,7 @@ export class LLMChatModelModule {
|
||||
const config = options.useFactory(...args);
|
||||
|
||||
switch (config?.type) {
|
||||
case LLMChatModelDriver.OpenAI: {
|
||||
case LLMChatModelDriver.OPENAI: {
|
||||
return new OpenAIDriver();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { ModuleMetadata, FactoryProvider } from '@nestjs/common';
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
import { LangfuseDriverOptions } from 'src/engine/core-modules/llm-tracing/drivers/langfuse.driver';
|
||||
|
||||
export enum LLMTracingDriver {
|
||||
Langfuse = 'langfuse',
|
||||
Console = 'console',
|
||||
LANGFUSE = 'LANGFUSE',
|
||||
CONSOLE = 'CONSOLE',
|
||||
}
|
||||
|
||||
export interface LangfuseDriverFactoryOptions {
|
||||
type: LLMTracingDriver.Langfuse;
|
||||
type: LLMTracingDriver.LANGFUSE;
|
||||
options: LangfuseDriverOptions;
|
||||
}
|
||||
|
||||
export interface ConsoleDriverFactoryOptions {
|
||||
type: LLMTracingDriver.Console;
|
||||
type: LLMTracingDriver.CONSOLE;
|
||||
}
|
||||
|
||||
export type LLMTracingModuleOptions =
|
||||
|
||||
@ -8,10 +8,10 @@ export const llmTracingModuleFactory = (
|
||||
const driver = twentyConfigService.get('LLM_TRACING_DRIVER');
|
||||
|
||||
switch (driver) {
|
||||
case LLMTracingDriver.Console: {
|
||||
return { type: LLMTracingDriver.Console as const };
|
||||
case LLMTracingDriver.CONSOLE: {
|
||||
return { type: LLMTracingDriver.CONSOLE as const };
|
||||
}
|
||||
case LLMTracingDriver.Langfuse: {
|
||||
case LLMTracingDriver.LANGFUSE: {
|
||||
const secretKey = twentyConfigService.get('LANGFUSE_SECRET_KEY');
|
||||
const publicKey = twentyConfigService.get('LANGFUSE_PUBLIC_KEY');
|
||||
|
||||
@ -22,7 +22,7 @@ export const llmTracingModuleFactory = (
|
||||
}
|
||||
|
||||
return {
|
||||
type: LLMTracingDriver.Langfuse as const,
|
||||
type: LLMTracingDriver.LANGFUSE as const,
|
||||
options: { secretKey, publicKey },
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Global, DynamicModule } from '@nestjs/common';
|
||||
import { DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
LLMTracingModuleAsyncOptions,
|
||||
LLMTracingDriver,
|
||||
LLMTracingModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/llm-tracing/interfaces/llm-tracing.interface';
|
||||
|
||||
import { LangfuseDriver } from 'src/engine/core-modules/llm-tracing/drivers/langfuse.driver';
|
||||
import { ConsoleDriver } from 'src/engine/core-modules/llm-tracing/drivers/console.driver';
|
||||
import { LLMTracingService } from 'src/engine/core-modules/llm-tracing/llm-tracing.service';
|
||||
import { LangfuseDriver } from 'src/engine/core-modules/llm-tracing/drivers/langfuse.driver';
|
||||
import { LLM_TRACING_DRIVER } from 'src/engine/core-modules/llm-tracing/llm-tracing.constants';
|
||||
import { LLMTracingService } from 'src/engine/core-modules/llm-tracing/llm-tracing.service';
|
||||
|
||||
@Global()
|
||||
export class LLMTracingModule {
|
||||
@ -20,10 +20,10 @@ export class LLMTracingModule {
|
||||
const config = options.useFactory(...args);
|
||||
|
||||
switch (config.type) {
|
||||
case LLMTracingDriver.Langfuse: {
|
||||
case LLMTracingDriver.LANGFUSE: {
|
||||
return new LangfuseDriver(config.options);
|
||||
}
|
||||
case LLMTracingDriver.Console: {
|
||||
case LLMTracingDriver.CONSOLE: {
|
||||
return new ConsoleDriver();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { LogLevel } from '@nestjs/common';
|
||||
|
||||
export enum LoggerDriverType {
|
||||
Console = 'console',
|
||||
CONSOLE = 'CONSOLE',
|
||||
}
|
||||
|
||||
export interface ConsoleDriverFactoryOptions {
|
||||
type: LoggerDriverType.Console;
|
||||
type: LoggerDriverType.CONSOLE;
|
||||
logLevels?: LogLevel[];
|
||||
}
|
||||
|
||||
|
||||
@ -16,9 +16,9 @@ export const loggerModuleFactory = async (
|
||||
const logLevels = twentyConfigService.get('LOG_LEVELS');
|
||||
|
||||
switch (driverType) {
|
||||
case LoggerDriverType.Console: {
|
||||
case LoggerDriverType.CONSOLE: {
|
||||
return {
|
||||
type: LoggerDriverType.Console,
|
||||
type: LoggerDriverType.CONSOLE,
|
||||
logLevels: logLevels,
|
||||
};
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export class LoggerModule extends ConfigurableModuleClass {
|
||||
const provider = {
|
||||
provide: LOGGER_DRIVER,
|
||||
useValue:
|
||||
options.type === LoggerDriverType.Console
|
||||
options.type === LoggerDriverType.CONSOLE
|
||||
? new ConsoleLogger()
|
||||
: undefined,
|
||||
};
|
||||
@ -45,7 +45,7 @@ export class LoggerModule extends ConfigurableModuleClass {
|
||||
const logLevels = config.logLevels ?? [];
|
||||
|
||||
const logger =
|
||||
config?.type === LoggerDriverType.Console
|
||||
config?.type === LoggerDriverType.CONSOLE
|
||||
? new ConsoleLogger()
|
||||
: undefined;
|
||||
|
||||
|
||||
@ -15,13 +15,13 @@ export const serverlessModuleFactory = async (
|
||||
const options = { fileStorageService };
|
||||
|
||||
switch (driverType) {
|
||||
case ServerlessDriverType.Local: {
|
||||
case ServerlessDriverType.LOCAL: {
|
||||
return {
|
||||
type: ServerlessDriverType.Local,
|
||||
type: ServerlessDriverType.LOCAL,
|
||||
options,
|
||||
};
|
||||
}
|
||||
case ServerlessDriverType.Lambda: {
|
||||
case ServerlessDriverType.LAMBDA: {
|
||||
const region = twentyConfigService.get('SERVERLESS_LAMBDA_REGION');
|
||||
const accessKeyId = twentyConfigService.get(
|
||||
'SERVERLESS_LAMBDA_ACCESS_KEY_ID',
|
||||
@ -36,7 +36,7 @@ export const serverlessModuleFactory = async (
|
||||
);
|
||||
|
||||
return {
|
||||
type: ServerlessDriverType.Lambda,
|
||||
type: ServerlessDriverType.LAMBDA,
|
||||
options: {
|
||||
...options,
|
||||
credentials: accessKeyId
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
import { LocalDriverOptions } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
import { LambdaDriverOptions } from 'src/engine/core-modules/serverless/drivers/lambda.driver';
|
||||
import { LocalDriverOptions } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
|
||||
export enum ServerlessDriverType {
|
||||
Lambda = 'lambda',
|
||||
Local = 'local',
|
||||
LAMBDA = 'LAMBDA',
|
||||
LOCAL = 'LOCAL',
|
||||
}
|
||||
|
||||
export interface LocalDriverFactoryOptions {
|
||||
type: ServerlessDriverType.Local;
|
||||
type: ServerlessDriverType.LOCAL;
|
||||
options: LocalDriverOptions;
|
||||
}
|
||||
|
||||
export interface LambdaDriverFactoryOptions {
|
||||
type: ServerlessDriverType.Lambda;
|
||||
type: ServerlessDriverType.LAMBDA;
|
||||
options: LambdaDriverOptions;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import { AddPackagesCommand } from 'src/engine/core-modules/serverless/commands/add-packages.command';
|
||||
import { LambdaDriver } from 'src/engine/core-modules/serverless/drivers/lambda.driver';
|
||||
import { LocalDriver } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
import { SERVERLESS_DRIVER } from 'src/engine/core-modules/serverless/serverless.constants';
|
||||
@ -8,7 +9,6 @@ import {
|
||||
ServerlessModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/serverless/serverless.interface';
|
||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||
import { AddPackagesCommand } from 'src/engine/core-modules/serverless/commands/add-packages.command';
|
||||
|
||||
@Global()
|
||||
export class ServerlessModule {
|
||||
@ -19,7 +19,7 @@ export class ServerlessModule {
|
||||
useFactory: async (...args: any[]) => {
|
||||
const config = await options.useFactory(...args);
|
||||
|
||||
return config?.type === ServerlessDriverType.Local
|
||||
return config?.type === ServerlessDriverType.LOCAL
|
||||
? new LocalDriver(config.options)
|
||||
: new LambdaDriver(config.options);
|
||||
},
|
||||
|
||||
@ -27,6 +27,7 @@ import { ServerlessDriverType } from 'src/engine/core-modules/serverless/serverl
|
||||
import { CastToLogLevelArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-log-level-array.decorator';
|
||||
import { CastToMeterDriverArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-meter-driver.decorator';
|
||||
import { CastToPositiveNumber } from 'src/engine/core-modules/twenty-config/decorators/cast-to-positive-number.decorator';
|
||||
import { CastToUpperSnakeCase } from 'src/engine/core-modules/twenty-config/decorators/cast-to-upper-snake-case.decorator';
|
||||
import { ConfigVariablesMetadata } from 'src/engine/core-modules/twenty-config/decorators/config-variables-metadata.decorator';
|
||||
import { IsAWSRegion } from 'src/engine/core-modules/twenty-config/decorators/is-aws-region.decorator';
|
||||
import { IsDuration } from 'src/engine/core-modules/twenty-config/decorators/is-duration.decorator';
|
||||
@ -301,7 +302,8 @@ export class ConfigVariables {
|
||||
type: ConfigVariableType.ENUM,
|
||||
options: Object.values(EmailDriver),
|
||||
})
|
||||
EMAIL_DRIVER: EmailDriver = EmailDriver.Logger;
|
||||
@CastToUpperSnakeCase()
|
||||
EMAIL_DRIVER: EmailDriver = EmailDriver.LOGGER;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.EmailSettings,
|
||||
@ -349,14 +351,15 @@ export class ConfigVariables {
|
||||
options: Object.values(StorageDriverType),
|
||||
})
|
||||
@IsOptional()
|
||||
STORAGE_TYPE: StorageDriverType = StorageDriverType.Local;
|
||||
@CastToUpperSnakeCase()
|
||||
STORAGE_TYPE: StorageDriverType = StorageDriverType.LOCAL;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.StorageConfig,
|
||||
description: 'Local path for storage when using local storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.Local)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.LOCAL)
|
||||
STORAGE_LOCAL_PATH = '.local-storage';
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -364,7 +367,7 @@ export class ConfigVariables {
|
||||
description: 'S3 region for storage when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsAWSRegion()
|
||||
STORAGE_S3_REGION: AwsRegion;
|
||||
|
||||
@ -373,7 +376,7 @@ export class ConfigVariables {
|
||||
description: 'S3 bucket name for storage when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
STORAGE_S3_NAME: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -381,7 +384,7 @@ export class ConfigVariables {
|
||||
description: 'S3 endpoint for storage when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsOptional()
|
||||
STORAGE_S3_ENDPOINT: string;
|
||||
|
||||
@ -392,7 +395,7 @@ export class ConfigVariables {
|
||||
'S3 access key ID for authentication when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsOptional()
|
||||
STORAGE_S3_ACCESS_KEY_ID: string;
|
||||
|
||||
@ -403,7 +406,7 @@ export class ConfigVariables {
|
||||
'S3 secret access key for authentication when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsOptional()
|
||||
STORAGE_S3_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@ -415,7 +418,8 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
SERVERLESS_TYPE: ServerlessDriverType = ServerlessDriverType.Local;
|
||||
@CastToUpperSnakeCase()
|
||||
SERVERLESS_TYPE: ServerlessDriverType = ServerlessDriverType.LOCAL;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ServerlessConfig,
|
||||
@ -439,7 +443,7 @@ export class ConfigVariables {
|
||||
description: 'Region for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsAWSRegion()
|
||||
SERVERLESS_LAMBDA_REGION: AwsRegion;
|
||||
|
||||
@ -448,7 +452,7 @@ export class ConfigVariables {
|
||||
description: 'IAM role for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
SERVERLESS_LAMBDA_ROLE: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -456,7 +460,7 @@ export class ConfigVariables {
|
||||
description: 'Role to assume when hosting lambdas in dedicated AWS account',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_SUBHOSTING_ROLE?: string;
|
||||
|
||||
@ -466,7 +470,7 @@ export class ConfigVariables {
|
||||
description: 'Access key ID for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID: string;
|
||||
|
||||
@ -476,7 +480,7 @@ export class ConfigVariables {
|
||||
description: 'Secret access key for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@ -634,8 +638,9 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@CastToUpperSnakeCase()
|
||||
EXCEPTION_HANDLER_DRIVER: ExceptionHandlerDriver =
|
||||
ExceptionHandlerDriver.Console;
|
||||
ExceptionHandlerDriver.CONSOLE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.Logging,
|
||||
@ -676,7 +681,8 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
LOGGER_DRIVER: LoggerDriverType = LoggerDriverType.Console;
|
||||
@CastToUpperSnakeCase()
|
||||
LOGGER_DRIVER: LoggerDriverType = LoggerDriverType.CONSOLE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ExceptionHandler,
|
||||
@ -685,7 +691,7 @@ export class ConfigVariables {
|
||||
isSensitive: true,
|
||||
})
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.SENTRY,
|
||||
)
|
||||
SENTRY_DSN: string;
|
||||
|
||||
@ -696,7 +702,7 @@ export class ConfigVariables {
|
||||
isSensitive: true,
|
||||
})
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.SENTRY,
|
||||
)
|
||||
SENTRY_FRONT_DSN: string;
|
||||
@ConfigVariablesMetadata({
|
||||
@ -705,7 +711,7 @@ export class ConfigVariables {
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.SENTRY,
|
||||
)
|
||||
@IsOptional()
|
||||
SENTRY_ENVIRONMENT: string;
|
||||
@ -717,7 +723,8 @@ export class ConfigVariables {
|
||||
options: Object.values(SupportDriver),
|
||||
})
|
||||
@IsOptional()
|
||||
SUPPORT_DRIVER: SupportDriver = SupportDriver.None;
|
||||
@CastToUpperSnakeCase()
|
||||
SUPPORT_DRIVER: SupportDriver = SupportDriver.NONE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SupportChatConfig,
|
||||
@ -725,7 +732,7 @@ export class ConfigVariables {
|
||||
description: 'Chat ID for the support front integration',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.FRONT)
|
||||
SUPPORT_FRONT_CHAT_ID: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -734,7 +741,7 @@ export class ConfigVariables {
|
||||
description: 'HMAC key for the support front integration',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.FRONT)
|
||||
SUPPORT_FRONT_HMAC_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -840,7 +847,8 @@ export class ConfigVariables {
|
||||
options: Object.values(NodeEnvironment),
|
||||
isEnvOnly: true,
|
||||
})
|
||||
NODE_ENV: NodeEnvironment = NodeEnvironment.production;
|
||||
// @CastToUpperSnakeCase()
|
||||
NODE_ENV: NodeEnvironment = NodeEnvironment.PRODUCTION;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ServerConfig,
|
||||
@ -949,6 +957,7 @@ export class ConfigVariables {
|
||||
options: Object.values(LLMChatModelDriver),
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@CastToUpperSnakeCase()
|
||||
LLM_CHAT_MODEL_DRIVER: LLMChatModelDriver;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -981,7 +990,8 @@ export class ConfigVariables {
|
||||
options: Object.values(LLMTracingDriver),
|
||||
isEnvOnly: true,
|
||||
})
|
||||
LLM_TRACING_DRIVER: LLMTracingDriver = LLMTracingDriver.Console;
|
||||
@CastToUpperSnakeCase()
|
||||
LLM_TRACING_DRIVER: LLMTracingDriver = LLMTracingDriver.CONSOLE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ServerConfig,
|
||||
@ -1059,6 +1069,7 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@CastToUpperSnakeCase()
|
||||
CAPTCHA_DRIVER?: CaptchaDriverType;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import { plainToClass } from 'class-transformer';
|
||||
|
||||
import { CastToUpperSnakeCase } from 'src/engine/core-modules/twenty-config/decorators/cast-to-upper-snake-case.decorator';
|
||||
|
||||
class TestClass {
|
||||
@CastToUpperSnakeCase()
|
||||
value: string;
|
||||
}
|
||||
|
||||
describe('CastToUpperSnakeCase Decorator', () => {
|
||||
it('should transform lowercase string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'local' });
|
||||
|
||||
expect(result.value).toBe('LOCAL');
|
||||
});
|
||||
|
||||
it('should transform camelCase string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'camelCase' });
|
||||
|
||||
expect(result.value).toBe('CAMEL_CASE');
|
||||
});
|
||||
|
||||
it('should transform kebab-case string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'kebab-case' });
|
||||
|
||||
expect(result.value).toBe('KEBAB_CASE');
|
||||
});
|
||||
|
||||
it('should transform space-separated string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'space separated' });
|
||||
|
||||
expect(result.value).toBe('SPACE_SEPARATED');
|
||||
});
|
||||
|
||||
it('should handle already UPPER_SNAKE_CASE string', () => {
|
||||
const result = plainToClass(TestClass, { value: 'ALREADY_UPPER_SNAKE' });
|
||||
|
||||
expect(result.value).toBe('ALREADY_UPPER_SNAKE');
|
||||
});
|
||||
|
||||
it('should handle mixed case with numbers', () => {
|
||||
const result = plainToClass(TestClass, { value: 'test123Value' });
|
||||
|
||||
expect(result.value).toBe('TEST_123_VALUE');
|
||||
});
|
||||
|
||||
it('should trim whitespace', () => {
|
||||
const result = plainToClass(TestClass, { value: ' local ' });
|
||||
|
||||
expect(result.value).toBe('LOCAL');
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const result = plainToClass(TestClass, { value: '' });
|
||||
|
||||
expect(result.value).toBe('');
|
||||
});
|
||||
|
||||
it('should return undefined for non-string values', () => {
|
||||
const result = plainToClass(TestClass, { value: 123 });
|
||||
|
||||
expect(result.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for null values', () => {
|
||||
const result = plainToClass(TestClass, { value: null });
|
||||
|
||||
expect(result.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for undefined values', () => {
|
||||
const result = plainToClass(TestClass, { value: undefined });
|
||||
|
||||
expect(result.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle complex mixed formats', () => {
|
||||
const result = plainToClass(TestClass, {
|
||||
value: 'Complex-Mixed_Format test123',
|
||||
});
|
||||
|
||||
expect(result.value).toBe('COMPLEX_MIXED_FORMAT_TEST_123');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,13 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
import snakeCase from 'lodash.snakecase';
|
||||
|
||||
export const CastToUpperSnakeCase = () =>
|
||||
Transform(({ value }: { value: string }) => toUpperSnakeCase(value));
|
||||
|
||||
const toUpperSnakeCase = (value: unknown): string | undefined => {
|
||||
if (typeof value === 'string') {
|
||||
return snakeCase(value.trim()).toUpperCase();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
export enum NodeEnvironment {
|
||||
test = 'test',
|
||||
development = 'development',
|
||||
production = 'production',
|
||||
TEST = 'test',
|
||||
DEVELOPMENT = 'development',
|
||||
PRODUCTION = 'production',
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum SupportDriver {
|
||||
None = 'none',
|
||||
Front = 'front',
|
||||
NONE = 'NONE',
|
||||
FRONT = 'FRONT',
|
||||
}
|
||||
|
||||
@ -201,9 +201,9 @@ export class TwentyConfigService {
|
||||
|
||||
getLoggingConfig(): LoggerOptions {
|
||||
switch (this.get('NODE_ENV')) {
|
||||
case NodeEnvironment.development:
|
||||
case NodeEnvironment.DEVELOPMENT:
|
||||
return ['query', 'error'];
|
||||
case NodeEnvironment.test:
|
||||
case NodeEnvironment.TEST:
|
||||
return [];
|
||||
default:
|
||||
return ['error'];
|
||||
|
||||
@ -123,7 +123,7 @@ export class UserResolver {
|
||||
) {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
@ -334,7 +334,7 @@ export class UserResolver {
|
||||
})
|
||||
supportUserHash(@Parent() parent: User): string | null {
|
||||
if (
|
||||
this.twentyConfigService.get('SUPPORT_DRIVER') !== SupportDriver.Front
|
||||
this.twentyConfigService.get('SUPPORT_DRIVER') !== SupportDriver.FRONT
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user