diff --git a/packages/twenty-server/src/core/analytics/analytics.service.ts b/packages/twenty-server/src/core/analytics/analytics.service.ts index b5b9a60c0..cb3f82241 100644 --- a/packages/twenty-server/src/core/analytics/analytics.service.ts +++ b/packages/twenty-server/src/core/analytics/analytics.service.ts @@ -23,12 +23,13 @@ export class AnalyticsService { workspace: Workspace | undefined, request: Request, ) { - if (!this.environmentService.isTelemetryEnabled()) { + if (!this.environmentService.get('TELEMETRY_ENABLED')) { return { success: true }; } - const anonymizationEnabled = - this.environmentService.isTelemetryAnonymizationEnabled(); + const anonymizationEnabled = this.environmentService.get( + 'TELEMETRY_ANONYMIZATION_ENABLED', + ); const data = { type: createEventInput.type, diff --git a/packages/twenty-server/src/core/api-rest/api-rest-query-builder/api-rest-query-builder.factory.ts b/packages/twenty-server/src/core/api-rest/api-rest-query-builder/api-rest-query-builder.factory.ts index 2fa25abfe..95ff90af8 100644 --- a/packages/twenty-server/src/core/api-rest/api-rest-query-builder/api-rest-query-builder.factory.ts +++ b/packages/twenty-server/src/core/api-rest/api-rest-query-builder/api-rest-query-builder.factory.ts @@ -47,7 +47,7 @@ export class ApiRestQueryBuilderFactory { if (!objectMetadataItems.length) { throw new BadRequestException( - `No object was found for the workspace associated with this API key. You may generate a new one here ${this.environmentService.getFrontBaseUrl()}/settings/developers`, + `No object was found for the workspace associated with this API key. You may generate a new one here ${this.environmentService.get('FRONT_BASE_URL')}/settings/developers`, ); } diff --git a/packages/twenty-server/src/core/auth/auth.module.ts b/packages/twenty-server/src/core/auth/auth.module.ts index 676704307..1ebeb8d69 100644 --- a/packages/twenty-server/src/core/auth/auth.module.ts +++ b/packages/twenty-server/src/core/auth/auth.module.ts @@ -30,9 +30,9 @@ import { AuthService } from './services/auth.service'; const jwtModule = JwtModule.registerAsync({ useFactory: async (environmentService: EnvironmentService) => { return { - secret: environmentService.getAccessTokenSecret(), + secret: environmentService.get('ACCESS_TOKEN_SECRET'), signOptions: { - expiresIn: environmentService.getAccessTokenExpiresIn(), + expiresIn: environmentService.get('ACCESS_TOKEN_EXPIRES_IN'), }, }; }, diff --git a/packages/twenty-server/src/core/auth/controllers/google-apis-auth.controller.ts b/packages/twenty-server/src/core/auth/controllers/google-apis-auth.controller.ts index 3a715807d..b7240e6a6 100644 --- a/packages/twenty-server/src/core/auth/controllers/google-apis-auth.controller.ts +++ b/packages/twenty-server/src/core/auth/controllers/google-apis-auth.controller.ts @@ -37,7 +37,7 @@ export class GoogleAPIsAuthController { const { workspaceMemberId, workspaceId } = await this.tokenService.verifyTransientToken(transientToken); - const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); + const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); if (demoWorkspaceIds.includes(workspaceId)) { throw new Error('Cannot connect Google account to demo workspace'); @@ -57,7 +57,7 @@ export class GoogleAPIsAuthController { }); return res.redirect( - `${this.environmentService.getFrontBaseUrl()}/settings/accounts`, + `${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`, ); } } diff --git a/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts b/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts index 292739f43..fe3490069 100644 --- a/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts +++ b/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts @@ -37,7 +37,7 @@ export class GoogleGmailAuthController { const { workspaceMemberId, workspaceId } = await this.tokenService.verifyTransientToken(transientToken); - const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); + const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); if (demoWorkspaceIds.includes(workspaceId)) { throw new Error('Cannot connect Gmail account to demo workspace'); @@ -58,7 +58,7 @@ export class GoogleGmailAuthController { }); return res.redirect( - `${this.environmentService.getFrontBaseUrl()}/settings/accounts`, + `${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`, ); } } diff --git a/packages/twenty-server/src/core/auth/guards/google-apis-provider-enabled.guard.ts b/packages/twenty-server/src/core/auth/guards/google-apis-provider-enabled.guard.ts index c0a344d18..38f5124ac 100644 --- a/packages/twenty-server/src/core/auth/guards/google-apis-provider-enabled.guard.ts +++ b/packages/twenty-server/src/core/auth/guards/google-apis-provider-enabled.guard.ts @@ -11,8 +11,8 @@ export class GoogleAPIsProviderEnabledGuard implements CanActivate { canActivate(): boolean | Promise | Observable { if ( - !this.environmentService.isMessagingProviderGmailEnabled() && - !this.environmentService.isCalendarProviderGoogleEnabled() + !this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') && + !this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') ) { throw new NotFoundException('Google apis auth is not enabled'); } diff --git a/packages/twenty-server/src/core/auth/guards/google-gmail-provider-enabled.guard.ts b/packages/twenty-server/src/core/auth/guards/google-gmail-provider-enabled.guard.ts new file mode 100644 index 000000000..a4c5a500c --- /dev/null +++ b/packages/twenty-server/src/core/auth/guards/google-gmail-provider-enabled.guard.ts @@ -0,0 +1,21 @@ +import { Injectable, CanActivate, NotFoundException } from '@nestjs/common'; + +import { Observable } from 'rxjs'; + +import { GoogleAPIsStrategy } from 'src/core/auth/strategies/google-apis.auth.strategy'; +import { EnvironmentService } from 'src/integrations/environment/environment.service'; + +@Injectable() +export class GoogleGmailProviderEnabledGuard implements CanActivate { + constructor(private readonly environmentService: EnvironmentService) {} + + canActivate(): boolean | Promise | Observable { + if (!this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) { + throw new NotFoundException('Gmail auth is not enabled'); + } + + new GoogleAPIsStrategy(this.environmentService); + + return true; + } +} diff --git a/packages/twenty-server/src/core/auth/guards/google-provider-enabled.guard.ts b/packages/twenty-server/src/core/auth/guards/google-provider-enabled.guard.ts index b6095fc95..91a84cdbf 100644 --- a/packages/twenty-server/src/core/auth/guards/google-provider-enabled.guard.ts +++ b/packages/twenty-server/src/core/auth/guards/google-provider-enabled.guard.ts @@ -10,7 +10,7 @@ export class GoogleProviderEnabledGuard implements CanActivate { constructor(private readonly environmentService: EnvironmentService) {} canActivate(): boolean | Promise | Observable { - if (!this.environmentService.isAuthGoogleEnabled()) { + if (!this.environmentService.get('AUTH_GOOGLE_ENABLED')) { throw new NotFoundException('Google auth is not enabled'); } diff --git a/packages/twenty-server/src/core/auth/services/auth.service.ts b/packages/twenty-server/src/core/auth/services/auth.service.ts index 9ebbd063b..9e8c5941b 100644 --- a/packages/twenty-server/src/core/auth/services/auth.service.ts +++ b/packages/twenty-server/src/core/auth/services/auth.service.ts @@ -194,7 +194,7 @@ export class AuthService { const emailTemplate = PasswordUpdateNotifyEmail({ userName: `${user.firstName} ${user.lastName}`, email: user.email, - link: this.environmentService.getFrontBaseUrl(), + link: this.environmentService.get('FRONT_BASE_URL'), }); const html = render(emailTemplate, { @@ -205,7 +205,7 @@ export class AuthService { }); this.emailService.send({ - from: `${this.environmentService.getEmailFromName()} <${this.environmentService.getEmailFromAddress()}>`, + from: `${this.environmentService.get('EMAIL_FROM_NAME')} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`, to: user.email, subject: 'Your Password Has Been Successfully Changed', text, diff --git a/packages/twenty-server/src/core/auth/services/google-apis.service.ts b/packages/twenty-server/src/core/auth/services/google-apis.service.ts index 1e1fffd3b..286e7cdd7 100644 --- a/packages/twenty-server/src/core/auth/services/google-apis.service.ts +++ b/packages/twenty-server/src/core/auth/services/google-apis.service.ts @@ -88,7 +88,7 @@ export class GoogleAPIsService { ], ); - if (this.environmentService.isMessagingProviderGmailEnabled()) { + if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) { await manager.query( `INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type") VALUES ($1, $2, $3, $4)`, ['share_everything', handle, connectedAccountId, 'email'], @@ -96,7 +96,7 @@ export class GoogleAPIsService { } if ( - this.environmentService.isCalendarProviderGoogleEnabled() && + this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') && IsCalendarEnabled ) { await manager.query( @@ -106,7 +106,7 @@ export class GoogleAPIsService { } }); - if (this.environmentService.isMessagingProviderGmailEnabled()) { + if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) { await this.messageQueueService.add( GmailFullSyncJob.name, { @@ -120,7 +120,7 @@ export class GoogleAPIsService { } if ( - this.environmentService.isCalendarProviderGoogleEnabled() && + this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') && IsCalendarEnabled ) { await this.calendarQueueService.add( diff --git a/packages/twenty-server/src/core/auth/services/sign-up.service.ts b/packages/twenty-server/src/core/auth/services/sign-up.service.ts index 21e8fbbe5..723282c09 100644 --- a/packages/twenty-server/src/core/auth/services/sign-up.service.ts +++ b/packages/twenty-server/src/core/auth/services/sign-up.service.ts @@ -190,7 +190,7 @@ export class SignUpService { } assert( - !this.environmentService.isSignUpDisabled(), + !this.environmentService.get('IS_SIGN_UP_DISABLED'), 'Sign up is disabled', ForbiddenException, ); diff --git a/packages/twenty-server/src/core/auth/services/token.service.ts b/packages/twenty-server/src/core/auth/services/token.service.ts index 825caef41..41742a3ad 100644 --- a/packages/twenty-server/src/core/auth/services/token.service.ts +++ b/packages/twenty-server/src/core/auth/services/token.service.ts @@ -63,7 +63,7 @@ export class TokenService { userId: string, workspaceId?: string, ): Promise { - const expiresIn = this.environmentService.getAccessTokenExpiresIn(); + const expiresIn = this.environmentService.get('ACCESS_TOKEN_EXPIRES_IN'); assert(expiresIn, '', InternalServerErrorException); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); @@ -93,8 +93,8 @@ export class TokenService { } async generateRefreshToken(userId: string): Promise { - const secret = this.environmentService.getRefreshTokenSecret(); - const expiresIn = this.environmentService.getRefreshTokenExpiresIn(); + const secret = this.environmentService.get('REFRESH_TOKEN_SECRET'); + const expiresIn = this.environmentService.get('REFRESH_TOKEN_EXPIRES_IN'); assert(expiresIn, '', InternalServerErrorException); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); @@ -124,8 +124,8 @@ export class TokenService { } async generateLoginToken(email: string): Promise { - const secret = this.environmentService.getLoginTokenSecret(); - const expiresIn = this.environmentService.getLoginTokenExpiresIn(); + const secret = this.environmentService.get('LOGIN_TOKEN_SECRET'); + const expiresIn = this.environmentService.get('LOGIN_TOKEN_EXPIRES_IN'); assert(expiresIn, '', InternalServerErrorException); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); @@ -146,8 +146,8 @@ export class TokenService { workspaceMemberId: string, workspaceId: string, ): Promise { - const secret = this.environmentService.getLoginTokenSecret(); - const expiresIn = this.environmentService.getTransientTokenExpiresIn(); + const secret = this.environmentService.get('LOGIN_TOKEN_SECRET'); + const expiresIn = this.environmentService.get('SHORT_TERM_TOKEN_EXPIRES_IN'); assert(expiresIn, '', InternalServerErrorException); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); @@ -176,7 +176,7 @@ export class TokenService { const jwtPayload = { sub: workspaceId, }; - const secret = this.environmentService.getAccessTokenSecret(); + const secret = this.environmentService.get('ACCESS_TOKEN_SECRET'); let expiresIn: string | number; if (expiresAt) { @@ -184,7 +184,7 @@ export class TokenService { (new Date(expiresAt).getTime() - new Date().getTime()) / 1000, ); } else { - expiresIn = this.environmentService.getApiTokenExpiresIn(); + expiresIn = this.environmentService.get('API_TOKEN_EXPIRES_IN'); } const token = this.jwtService.sign(jwtPayload, { secret, @@ -209,7 +209,7 @@ export class TokenService { } const decoded = await this.verifyJwt( token, - this.environmentService.getAccessTokenSecret(), + this.environmentService.get('ACCESS_TOKEN_SECRET'), ); const { user, workspace } = await this.jwtStrategy.validate( @@ -220,7 +220,7 @@ export class TokenService { } async verifyLoginToken(loginToken: string): Promise { - const loginTokenSecret = this.environmentService.getLoginTokenSecret(); + const loginTokenSecret = this.environmentService.get('LOGIN_TOKEN_SECRET'); const payload = await this.verifyJwt(loginToken, loginTokenSecret); @@ -231,7 +231,7 @@ export class TokenService { workspaceMemberId: string; workspaceId: string; }> { - const transientTokenSecret = this.environmentService.getLoginTokenSecret(); + const transientTokenSecret = this.environmentService.get('LOGIN_TOKEN_SECRET'); const payload = await this.verifyJwt(transientToken, transientTokenSecret); @@ -281,8 +281,8 @@ export class TokenService { } async verifyRefreshToken(refreshToken: string) { - const secret = this.environmentService.getRefreshTokenSecret(); - const coolDown = this.environmentService.getRefreshTokenCoolDown(); + const secret = this.environmentService.get('REFRESH_TOKEN_SECRET'); + const coolDown = this.environmentService.get('REFRESH_TOKEN_COOL_DOWN'); const jwtPayload = await this.verifyJwt(refreshToken, secret); assert( @@ -382,7 +382,7 @@ export class TokenService { assert(user, 'User not found', NotFoundException); - const expiresIn = this.environmentService.getPasswordResetTokenExpiresIn(); + const expiresIn = this.environmentService.get('PASSWORD_RESET_TOKEN_EXPIRES_IN'); assert( expiresIn, @@ -439,7 +439,7 @@ export class TokenService { assert(user, 'User not found', NotFoundException); - const frontBaseURL = this.environmentService.getFrontBaseUrl(); + const frontBaseURL = this.environmentService.get('FRONT_BASE_URL'); const resetLink = `${frontBaseURL}/reset-password/${resetToken.passwordResetToken}`; const emailData = { @@ -465,7 +465,7 @@ export class TokenService { }); this.emailService.send({ - from: `${this.environmentService.getEmailFromName()} <${this.environmentService.getEmailFromAddress()}>`, + from: `${this.environmentService.get('EMAIL_FROM_NAME')} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`, to: email, subject: 'Action Needed to Reset Password', text, diff --git a/packages/twenty-server/src/core/auth/strategies/google-apis.auth.strategy.ts b/packages/twenty-server/src/core/auth/strategies/google-apis.auth.strategy.ts index 91a9f624f..e0633e072 100644 --- a/packages/twenty-server/src/core/auth/strategies/google-apis.auth.strategy.ts +++ b/packages/twenty-server/src/core/auth/strategies/google-apis.auth.strategy.ts @@ -27,20 +27,20 @@ export class GoogleAPIsStrategy extends PassportStrategy( constructor(environmentService: EnvironmentService) { const scope = ['email', 'profile']; - if (environmentService.isMessagingProviderGmailEnabled()) { + if (environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) { scope.push('https://www.googleapis.com/auth/gmail.readonly'); } - if (environmentService.isCalendarProviderGoogleEnabled()) { + if (environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED')) { scope.push('https://www.googleapis.com/auth/calendar'); } super({ - clientID: environmentService.getAuthGoogleClientId(), - clientSecret: environmentService.getAuthGoogleClientSecret(), - callbackURL: environmentService.isCalendarProviderGoogleEnabled() - ? environmentService.getAuthGoogleAPIsCallbackUrl() - : environmentService.getMessagingProviderGmailCallbackUrl(), + clientID: environmentService.get('AUTH_GOOGLE_CLIENT_ID'), + clientSecret: environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'), + callbackURL: environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') + ? environmentService.get('AUTH_GOOGLE_APIS_CALLBACK_URL') + : environmentService.get('MESSAGING_PROVIDER_GMAIL_CALLBACK_URL'), scope, passReqToCallback: true, }); diff --git a/packages/twenty-server/src/core/auth/strategies/google.auth.strategy.ts b/packages/twenty-server/src/core/auth/strategies/google.auth.strategy.ts index d8a7c6e5c..670307ec8 100644 --- a/packages/twenty-server/src/core/auth/strategies/google.auth.strategy.ts +++ b/packages/twenty-server/src/core/auth/strategies/google.auth.strategy.ts @@ -20,9 +20,9 @@ export type GoogleRequest = Request & { export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { constructor(environmentService: EnvironmentService) { super({ - clientID: environmentService.getAuthGoogleClientId(), - clientSecret: environmentService.getAuthGoogleClientSecret(), - callbackURL: environmentService.getAuthGoogleCallbackUrl(), + clientID: environmentService.get('AUTH_GOOGLE_CLIENT_ID'), + clientSecret: environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'), + callbackURL: environmentService.get('AUTH_GOOGLE_CALLBACK_URL'), scope: ['email', 'profile'], passReqToCallback: true, }); diff --git a/packages/twenty-server/src/core/auth/strategies/jwt.auth.strategy.ts b/packages/twenty-server/src/core/auth/strategies/jwt.auth.strategy.ts index 32e4db5fc..613fd5325 100644 --- a/packages/twenty-server/src/core/auth/strategies/jwt.auth.strategy.ts +++ b/packages/twenty-server/src/core/auth/strategies/jwt.auth.strategy.ts @@ -33,7 +33,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, - secretOrKey: environmentService.getAccessTokenSecret(), + secretOrKey: environmentService.get('ACCESS_TOKEN_SECRET'), }); } diff --git a/packages/twenty-server/src/core/billing/billing.service.ts b/packages/twenty-server/src/core/billing/billing.service.ts index 2a5459082..4e321b44c 100644 --- a/packages/twenty-server/src/core/billing/billing.service.ts +++ b/packages/twenty-server/src/core/billing/billing.service.ts @@ -42,7 +42,7 @@ export class BillingService { getProductStripeId(product: AvailableProduct) { if (product === AvailableProduct.BasePlan) { - return this.environmentService.getBillingStripeBasePlanProductId(); + return this.environmentService.get('BILLING_STRIPE_BASE_PLAN_PRODUCT_ID'); } } @@ -98,7 +98,7 @@ export class BillingService { async getBillingSubscriptionItem( workspaceId: string, - stripeProductId = this.environmentService.getBillingStripeBasePlanProductId(), + stripeProductId = this.environmentService.get('BILLING_STRIPE_BASE_PLAN_PRODUCT_ID'), ) { const billingSubscription = await this.getCurrentBillingSubscription({ workspaceId, @@ -134,7 +134,7 @@ export class BillingService { where: { workspaceId }, }); - const frontBaseUrl = this.environmentService.getFrontBaseUrl(); + const frontBaseUrl = this.environmentService.get('FRONT_BASE_URL'); const returnUrl = returnUrlPath ? frontBaseUrl + returnUrlPath : frontBaseUrl; @@ -154,7 +154,7 @@ export class BillingService { priceId: string, successUrlPath?: string, ): Promise { - const frontBaseUrl = this.environmentService.getFrontBaseUrl(); + const frontBaseUrl = this.environmentService.get('FRONT_BASE_URL'); const successUrl = successUrlPath ? frontBaseUrl + successUrlPath : frontBaseUrl; diff --git a/packages/twenty-server/src/core/billing/stripe/stripe.service.ts b/packages/twenty-server/src/core/billing/stripe/stripe.service.ts index 24afa8bfa..2ca32c631 100644 --- a/packages/twenty-server/src/core/billing/stripe/stripe.service.ts +++ b/packages/twenty-server/src/core/billing/stripe/stripe.service.ts @@ -12,14 +12,14 @@ export class StripeService { constructor(private readonly environmentService: EnvironmentService) { this.stripe = new Stripe( - this.environmentService.getBillingStripeApiKey(), + this.environmentService.get('BILLING_STRIPE_API_KEY'), {}, ); } constructEventFromPayload(signature: string, payload: Buffer) { const webhookSecret = - this.environmentService.getBillingStripeWebhookSecret(); + this.environmentService.get('BILLING_STRIPE_WEBHOOK_SECRET'); return this.stripe.webhooks.constructEvent( payload, @@ -48,7 +48,7 @@ export class StripeService { ): Promise { return await this.stripe.billingPortal.sessions.create({ customer: stripeCustomerId, - return_url: returnUrl ?? this.environmentService.getFrontBaseUrl(), + return_url: returnUrl ?? this.environmentService.get('FRONT_BASE_URL'), }); } @@ -73,7 +73,7 @@ export class StripeService { workspaceId: user.defaultWorkspace.id, }, trial_period_days: - this.environmentService.getBillingFreeTrialDurationInDays(), + this.environmentService.get('BILLING_FREE_TRIAL_DURATION_IN_DAYS'), }, automatic_tax: { enabled: true }, tax_id_collection: { enabled: true }, diff --git a/packages/twenty-server/src/core/client-config/client-config.resolver.ts b/packages/twenty-server/src/core/client-config/client-config.resolver.ts index 7ee9d292a..70e0aedbc 100644 --- a/packages/twenty-server/src/core/client-config/client-config.resolver.ts +++ b/packages/twenty-server/src/core/client-config/client-config.resolver.ts @@ -12,30 +12,34 @@ export class ClientConfigResolver { async clientConfig(): Promise { const clientConfig: ClientConfig = { authProviders: { - google: this.environmentService.isAuthGoogleEnabled(), + google: this.environmentService.get('AUTH_GOOGLE_ENABLED'), magicLink: false, password: true, }, telemetry: { - enabled: this.environmentService.isTelemetryEnabled(), - anonymizationEnabled: - this.environmentService.isTelemetryAnonymizationEnabled(), + enabled: this.environmentService.get('TELEMETRY_ENABLED'), + anonymizationEnabled: this.environmentService.get( + 'TELEMETRY_ANONYMIZATION_ENABLED', + ), }, billing: { - isBillingEnabled: this.environmentService.isBillingEnabled(), - billingUrl: this.environmentService.getBillingUrl(), - billingFreeTrialDurationInDays: - this.environmentService.getBillingFreeTrialDurationInDays(), + isBillingEnabled: this.environmentService.get('IS_BILLING_ENABLED'), + billingUrl: this.environmentService.get('BILLING_PLAN_REQUIRED_LINK'), + billingFreeTrialDurationInDays: this.environmentService.get( + 'BILLING_FREE_TRIAL_DURATION_IN_DAYS', + ), }, - signInPrefilled: this.environmentService.isSignInPrefilled(), - signUpDisabled: this.environmentService.isSignUpDisabled(), - debugMode: this.environmentService.isDebugMode(), + signInPrefilled: this.environmentService.get('SIGN_IN_PREFILLED'), + signUpDisabled: this.environmentService.get('IS_SIGN_UP_DISABLED'), + debugMode: this.environmentService.get('DEBUG_MODE'), support: { - supportDriver: this.environmentService.getSupportDriver(), - supportFrontChatId: this.environmentService.getSupportFrontChatId(), + supportDriver: this.environmentService.get('SUPPORT_DRIVER'), + supportFrontChatId: this.environmentService.get( + 'SUPPORT_FRONT_CHAT_ID', + ), }, sentry: { - dsn: this.environmentService.getSentryDSN(), + dsn: this.environmentService.get('SENTRY_DSN'), }, }; diff --git a/packages/twenty-server/src/core/quick-actions/intelligence.service.ts b/packages/twenty-server/src/core/quick-actions/intelligence.service.ts index cdae9066f..ee0540c9d 100644 --- a/packages/twenty-server/src/core/quick-actions/intelligence.service.ts +++ b/packages/twenty-server/src/core/quick-actions/intelligence.service.ts @@ -37,7 +37,7 @@ export class IntelligenceService { 'https://openrouter.ai/api/v1/chat/completions', { headers: { - Authorization: `Bearer ${this.environmentService.getOpenRouterApiKey()}`, + Authorization: `Bearer ${this.environmentService.get('OPENROUTER_API_KEY')}`, 'HTTP-Referer': `https://twenty.com`, 'X-Title': `Twenty CRM`, 'Content-Type': 'application/json', diff --git a/packages/twenty-server/src/core/user/user.resolver.ts b/packages/twenty-server/src/core/user/user.resolver.ts index 595f6a6df..0a429d138 100644 --- a/packages/twenty-server/src/core/user/user.resolver.ts +++ b/packages/twenty-server/src/core/user/user.resolver.ts @@ -76,10 +76,10 @@ export class UserResolver { nullable: true, }) supportUserHash(@Parent() parent: User): string | null { - if (this.environmentService.getSupportDriver() !== SupportDriver.Front) { + if (this.environmentService.get('SUPPORT_DRIVER') !== SupportDriver.Front) { return null; } - const key = this.environmentService.getSupportFrontHMACKey(); + const key = this.environmentService.get('SUPPORT_FRONT_HMAC_KEY'); return getHMACKey(parent.email, key); } @@ -111,7 +111,7 @@ export class UserResolver { @Mutation(() => User) async deleteUser(@AuthUser() { id: userId, defaultWorkspace }: User) { // Get the list of demo workspace IDs - const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); + const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); const currentUserWorkspaceId = defaultWorkspace.id; diff --git a/packages/twenty-server/src/core/workspace/workspace.resolver.ts b/packages/twenty-server/src/core/workspace/workspace.resolver.ts index 6b398847b..fe0890da6 100644 --- a/packages/twenty-server/src/core/workspace/workspace.resolver.ts +++ b/packages/twenty-server/src/core/workspace/workspace.resolver.ts @@ -91,7 +91,7 @@ export class WorkspaceResolver { @Mutation(() => Workspace) async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) { - const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); + const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); // Check if the id is in the list of demo workspaceIds if (demoWorkspaceIds.includes(id)) { diff --git a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/services/data-seed-demo-workspace.service.ts b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/services/data-seed-demo-workspace.service.ts index d6553bc95..cb0721f28 100644 --- a/packages/twenty-server/src/database/commands/data-seed-demo-workspace/services/data-seed-demo-workspace.service.ts +++ b/packages/twenty-server/src/database/commands/data-seed-demo-workspace/services/data-seed-demo-workspace.service.ts @@ -19,14 +19,14 @@ export class DataSeedDemoWorkspaceService { async seedDemo(): Promise { try { const dataSource = new DataSource({ - url: this.environmentService.getPGDatabaseUrl(), + url: this.environmentService.get('PG_DATABASE_URL'), type: 'postgres', logging: true, schema: 'public', }); await dataSource.initialize(); - const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); + const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); if (demoWorkspaceIds.length === 0) { throw new Error( diff --git a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts index da8810dda..40a3c69d0 100644 --- a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts +++ b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts @@ -38,7 +38,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner { async run(): Promise { try { const dataSource = new DataSource({ - url: this.environmentService.getPGDatabaseUrl(), + url: this.environmentService.get('PG_DATABASE_URL'), type: 'postgres', logging: true, schema: 'core', diff --git a/packages/twenty-server/src/database/typeorm/typeorm.service.ts b/packages/twenty-server/src/database/typeorm/typeorm.service.ts index 1aa3f260f..9cf3d2501 100644 --- a/packages/twenty-server/src/database/typeorm/typeorm.service.ts +++ b/packages/twenty-server/src/database/typeorm/typeorm.service.ts @@ -20,7 +20,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy { constructor(private readonly environmentService: EnvironmentService) { this.mainDataSource = new DataSource({ - url: environmentService.getPGDatabaseUrl(), + url: environmentService.get('PG_DATABASE_URL'), type: 'postgres', logging: false, schema: 'core', @@ -83,9 +83,9 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy { const schema = dataSource.schema; const workspaceDataSource = new DataSource({ - url: dataSource.url ?? this.environmentService.getPGDatabaseUrl(), + url: dataSource.url ?? this.environmentService.get('PG_DATABASE_URL'), type: 'postgres', - logging: this.environmentService.isDebugMode() + logging: this.environmentService.get('DEBUG_MODE') ? ['query', 'error'] : ['error'], schema, diff --git a/packages/twenty-server/src/graphql-config/graphql-config.service.ts b/packages/twenty-server/src/graphql-config/graphql-config.service.ts index 138d7771c..62b91ee16 100644 --- a/packages/twenty-server/src/graphql-config/graphql-config.service.ts +++ b/packages/twenty-server/src/graphql-config/graphql-config.service.ts @@ -46,11 +46,11 @@ export class GraphQLConfigService ) {} createGqlOptions(): YogaDriverConfig { - const isDebugMode = this.environmentService.isDebugMode(); + const isDebugMode = this.environmentService.get('DEBUG_MODE'); const plugins = [ useThrottler({ - ttl: this.environmentService.getApiRateLimitingTtl(), - limit: this.environmentService.getApiRateLimitingLimit(), + ttl: this.environmentService.get('API_RATE_LIMITING_TTL'), + limit: this.environmentService.get('API_RATE_LIMITING_LIMIT'), identifyFn: (context) => { return context.user?.id ?? context.req.ip ?? 'anonymous'; }, diff --git a/packages/twenty-server/src/integrations/cache-storage/cache-storage.module-factory.ts b/packages/twenty-server/src/integrations/cache-storage/cache-storage.module-factory.ts index b50872b9a..b6957c808 100644 --- a/packages/twenty-server/src/integrations/cache-storage/cache-storage.module-factory.ts +++ b/packages/twenty-server/src/integrations/cache-storage/cache-storage.module-factory.ts @@ -8,8 +8,8 @@ import { EnvironmentService } from 'src/integrations/environment/environment.ser export const cacheStorageModuleFactory = ( environmentService: EnvironmentService, ): CacheModuleOptions => { - const cacheStorageType = environmentService.getCacheStorageType(); - const cacheStorageTtl = environmentService.getCacheStorageTtl(); + const cacheStorageType = environmentService.get('CACHE_STORAGE_TYPE'); + const cacheStorageTtl = environmentService.get('CACHE_STORAGE_TTL'); const cacheModuleOptions: CacheModuleOptions = { isGlobal: true, ttl: cacheStorageTtl * 1000, @@ -20,8 +20,8 @@ export const cacheStorageModuleFactory = ( return cacheModuleOptions; } case CacheStorageType.Redis: { - const host = environmentService.getRedisHost(); - const port = environmentService.getRedisPort(); + const host = environmentService.get('REDIS_HOST'); + const port = environmentService.get('REDIS_PORT'); if (!(host && port)) { throw new Error( diff --git a/packages/twenty-server/src/integrations/email/email.module-factory.ts b/packages/twenty-server/src/integrations/email/email.module-factory.ts index c7da0d8d8..6b9a52d1a 100644 --- a/packages/twenty-server/src/integrations/email/email.module-factory.ts +++ b/packages/twenty-server/src/integrations/email/email.module-factory.ts @@ -8,17 +8,17 @@ import { EnvironmentService } from 'src/integrations/environment/environment.ser export const emailModuleFactory = ( environmentService: EnvironmentService, ): EmailModuleOptions => { - const driver = environmentService.getEmailDriver(); + const driver = environmentService.get('EMAIL_DRIVER'); switch (driver) { case EmailDriver.Logger: { return; } case EmailDriver.Smtp: { - const host = environmentService.getEmailHost(); - const port = environmentService.getEmailPort(); - const user = environmentService.getEmailUser(); - const pass = environmentService.getEmailPassword(); + const host = environmentService.get('EMAIL_SMTP_HOST'); + const port = environmentService.get('EMAIL_SMTP_PORT'); + const user = environmentService.get('EMAIL_SMTP_USER'); + const pass = environmentService.get('EMAIL_SMTP_PASSWORD'); if (!(host && port)) { throw new Error( diff --git a/packages/twenty-server/src/integrations/environment/environment.validation.ts b/packages/twenty-server/src/integrations/environment/environment-variables.ts similarity index 72% rename from packages/twenty-server/src/integrations/environment/environment.validation.ts rename to packages/twenty-server/src/integrations/environment/environment-variables.ts index e64df45c6..7aad5e722 100644 --- a/packages/twenty-server/src/integrations/environment/environment.validation.ts +++ b/packages/twenty-server/src/integrations/environment/environment-variables.ts @@ -10,8 +10,11 @@ import { validateSync, IsBoolean, IsNumber, + IsDefined, } from 'class-validator'; +import { EmailDriver } from 'src/integrations/email/interfaces/email.interface'; + import { assert } from 'src/utils/assert'; import { CastToStringArray } from 'src/integrations/environment/decorators/cast-to-string-array.decorator'; import { ExceptionHandlerDriver } from 'src/integrations/exception-handler/interfaces'; @@ -32,49 +35,49 @@ export class EnvironmentVariables { @CastToBoolean() @IsOptional() @IsBoolean() - DEBUG_MODE?: boolean; + DEBUG_MODE: boolean; @CastToBoolean() @IsOptional() @IsBoolean() - SIGN_IN_PREFILLED?: boolean; + SIGN_IN_PREFILLED: boolean; @CastToBoolean() @IsOptional() @IsBoolean() - IS_BILLING_ENABLED?: boolean; + IS_BILLING_ENABLED: boolean; @IsString() @ValidateIf((env) => env.IS_BILLING_ENABLED === true) - BILLING_PLAN_REQUIRED_LINK?: string; + BILLING_PLAN_REQUIRED_LINK: string; @IsString() @ValidateIf((env) => env.IS_BILLING_ENABLED === true) - BILLING_STRIPE_BASE_PLAN_PRODUCT_ID?: string; + BILLING_STRIPE_BASE_PLAN_PRODUCT_ID: string; @IsNumber() @CastToPositiveNumber() @IsOptional() @ValidateIf((env) => env.IS_BILLING_ENABLED === true) - BILLING_FREE_TRIAL_DURATION_IN_DAYS?: number; + BILLING_FREE_TRIAL_DURATION_IN_DAYS: number; @IsString() @ValidateIf((env) => env.IS_BILLING_ENABLED === true) - BILLING_STRIPE_API_KEY?: string; + BILLING_STRIPE_API_KEY: string; @IsString() @ValidateIf((env) => env.IS_BILLING_ENABLED === true) - BILLING_STRIPE_WEBHOOK_SECRET?: string; + BILLING_STRIPE_WEBHOOK_SECRET: string; @CastToBoolean() @IsOptional() @IsBoolean() - TELEMETRY_ENABLED?: boolean; + TELEMETRY_ENABLED: boolean; @CastToBoolean() @IsOptional() @IsBoolean() - TELEMETRY_ANONYMIZATION_ENABLED?: boolean; + TELEMETRY_ANONYMIZATION_ENABLED: boolean; @CastToPositiveNumber() @IsNumber() @@ -82,6 +85,7 @@ export class EnvironmentVariables { PORT: number; // Database + @IsDefined() @IsUrl({ protocols: ['postgres'], require_tld: false, @@ -132,75 +136,79 @@ export class EnvironmentVariables { @CastToBoolean() @IsOptional() @IsBoolean() - AUTH_GOOGLE_ENABLED?: boolean; + AUTH_GOOGLE_ENABLED: boolean; @IsString() @ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true) - AUTH_GOOGLE_CLIENT_ID?: string; + AUTH_GOOGLE_CLIENT_ID: string; @IsString() @ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true) - AUTH_GOOGLE_CLIENT_SECRET?: string; + AUTH_GOOGLE_CLIENT_SECRET: string; @IsUrl({ require_tld: false }) @ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true) - AUTH_GOOGLE_CALLBACK_URL?: string; + AUTH_GOOGLE_CALLBACK_URL: string; // Storage @IsEnum(StorageDriverType) @IsOptional() - STORAGE_TYPE?: StorageDriverType; + STORAGE_TYPE: StorageDriverType; @ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3) @IsAWSRegion() - STORAGE_S3_REGION?: AwsRegion; + STORAGE_S3_REGION: AwsRegion; @ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3) @IsString() - STORAGE_S3_NAME?: string; + 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; + STORAGE_LOCAL_PATH: string; // Support @IsEnum(SupportDriver) @IsOptional() - SUPPORT_DRIVER?: SupportDriver; + SUPPORT_DRIVER: SupportDriver; @ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front) @IsString() - SUPPORT_FRONT_CHAT_ID?: string; + SUPPORT_FRONT_CHAT_ID: string; @ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front) @IsString() - SUPPORT_FRONT_HMAC_KEY?: string; + SUPPORT_FRONT_HMAC_KEY: string; @IsEnum(LoggerDriverType) @IsOptional() - LOGGER_DRIVER?: LoggerDriverType; + LOGGER_DRIVER: LoggerDriverType; @IsEnum(ExceptionHandlerDriver) @IsOptional() - EXCEPTION_HANDLER_DRIVER?: ExceptionHandlerDriver; + EXCEPTION_HANDLER_DRIVER: ExceptionHandlerDriver; @CastToLogLevelArray() @IsOptional() - LOG_LEVELS?: LogLevel[]; + LOG_LEVELS: LogLevel[]; @CastToStringArray() @IsOptional() - DEMO_WORKSPACE_IDS?: string[]; + DEMO_WORKSPACE_IDS: string[]; @ValidateIf( (env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry, ) @IsString() - SENTRY_DSN?: string; + SENTRY_DSN: string; @IsDuration() @IsOptional() - PASSWORD_RESET_TOKEN_EXPIRES_IN?: number; + PASSWORD_RESET_TOKEN_EXPIRES_IN: string; @CastToPositiveNumber() @IsNumber() @@ -220,13 +228,56 @@ export class EnvironmentVariables { @CastToBoolean() @IsOptional() @IsBoolean() - IS_SIGN_UP_DISABLED?: boolean; + 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) => { const validatedConfig = plainToClass(EnvironmentVariables, config); diff --git a/packages/twenty-server/src/integrations/environment/environment.default.ts b/packages/twenty-server/src/integrations/environment/environment.default.ts new file mode 100644 index 000000000..18dea0084 --- /dev/null +++ b/packages/twenty-server/src/integrations/environment/environment.default.ts @@ -0,0 +1,77 @@ +import { EmailDriver } from 'src/integrations/email/interfaces/email.interface'; +import { SupportDriver } from 'src/integrations/environment/interfaces/support.interface'; + +import { ExceptionHandlerDriver } from 'src/integrations/exception-handler/interfaces'; +import { StorageDriverType } from 'src/integrations/file-storage/interfaces'; +import { LoggerDriverType } from 'src/integrations/logger/interfaces'; +import { MessageQueueDriverType } from 'src/integrations/message-queue/interfaces'; +import { EnvironmentVariables } from 'src/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 }; diff --git a/packages/twenty-server/src/integrations/environment/environment.module.ts b/packages/twenty-server/src/integrations/environment/environment.module.ts index b116f17ff..c42fd6ee9 100644 --- a/packages/twenty-server/src/integrations/environment/environment.module.ts +++ b/packages/twenty-server/src/integrations/environment/environment.module.ts @@ -3,7 +3,7 @@ import { ConfigModule } from '@nestjs/config'; import { EnvironmentService } from './environment.service'; import { ConfigurableModuleClass } from './environment.module-definition'; -import { validate } from './environment.validation'; +import { validate } from './environment-variables'; @Global() @Module({ diff --git a/packages/twenty-server/src/integrations/environment/environment.service.ts b/packages/twenty-server/src/integrations/environment/environment.service.ts index 05ee2184e..2dc8d48c7 100644 --- a/packages/twenty-server/src/integrations/environment/environment.service.ts +++ b/packages/twenty-server/src/integrations/environment/environment.service.ts @@ -1,92 +1,23 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { Injectable, LogLevel } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Request } from 'express'; -import { EmailDriver } from 'src/integrations/email/interfaces/email.interface'; - -import { LoggerDriverType } from 'src/integrations/logger/interfaces'; -import { ExceptionHandlerDriver } from 'src/integrations/exception-handler/interfaces'; -import { StorageDriverType } from 'src/integrations/file-storage/interfaces'; -import { MessageQueueDriverType } from 'src/integrations/message-queue/interfaces'; - -import { AwsRegion } from './interfaces/aws-region.interface'; -import { SupportDriver } from './interfaces/support.interface'; +import { EnvironmentVariables } from 'src/integrations/environment/environment-variables'; +import { EnvironmentDefault } from 'src/integrations/environment/environment.default'; @Injectable() export class EnvironmentService { constructor(private configService: ConfigService) {} - isDebugMode(): boolean { - return this.configService.get('DEBUG_MODE') ?? false; - } - - isSignInPrefilled(): boolean { - return this.configService.get('SIGN_IN_PREFILLED') ?? false; - } - - isBillingEnabled() { - return this.configService.get('IS_BILLING_ENABLED') ?? false; - } - - getBillingUrl() { - return this.configService.get('BILLING_PLAN_REQUIRED_LINK') ?? ''; - } - - getBillingStripeBasePlanProductId(): string { + get(key: T): EnvironmentVariables[T] { return ( - this.configService.get('BILLING_STRIPE_BASE_PLAN_PRODUCT_ID') ?? - '' + this.configService.get(key) ?? + EnvironmentDefault[key] ); } - getBillingStripeApiKey(): string { - return this.configService.get('BILLING_STRIPE_API_KEY') ?? ''; - } - - getBillingStripeWebhookSecret(): string { - return ( - this.configService.get('BILLING_STRIPE_WEBHOOK_SECRET') ?? '' - ); - } - - getBillingFreeTrialDurationInDays(): number { - return ( - this.configService.get('BILLING_FREE_TRIAL_DURATION_IN_DAYS') ?? 7 - ); - } - - isTelemetryEnabled(): boolean { - return this.configService.get('TELEMETRY_ENABLED') ?? true; - } - - isTelemetryAnonymizationEnabled(): boolean { - return ( - this.configService.get('TELEMETRY_ANONYMIZATION_ENABLED') ?? true - ); - } - - getPort(): number { - return this.configService.get('PORT') ?? 3000; - } - - getPGDatabaseUrl(): string { - return this.configService.get('PG_DATABASE_URL')!; - } - - getRedisHost(): string { - return this.configService.get('REDIS_HOST') ?? '127.0.0.1'; - } - - getRedisPort(): number { - return +(this.configService.get('REDIS_PORT') ?? 6379); - } - - getFrontBaseUrl(): string { - return this.configService.get('FRONT_BASE_URL')!; - } - getServerUrl(): string { const url = this.configService.get('SERVER_URL')!; @@ -103,262 +34,15 @@ export class EnvironmentService { ); } - getAccessTokenSecret(): string { - return this.configService.get('ACCESS_TOKEN_SECRET')!; - } - - getAccessTokenExpiresIn(): string { - return this.configService.get('ACCESS_TOKEN_EXPIRES_IN') ?? '30m'; - } - - getRefreshTokenSecret(): string { - return this.configService.get('REFRESH_TOKEN_SECRET')!; - } - - getRefreshTokenExpiresIn(): string { - return this.configService.get('REFRESH_TOKEN_EXPIRES_IN') ?? '90d'; - } - - getRefreshTokenCoolDown(): string { - return this.configService.get('REFRESH_TOKEN_COOL_DOWN') ?? '1m'; - } - - getLoginTokenSecret(): string { - return this.configService.get('LOGIN_TOKEN_SECRET')!; - } - - getLoginTokenExpiresIn(): string { - return this.configService.get('LOGIN_TOKEN_EXPIRES_IN') ?? '15m'; - } - - getTransientTokenExpiresIn(): string { - return ( - this.configService.get('SHORT_TERM_TOKEN_EXPIRES_IN') ?? '5m' - ); - } - - getApiTokenExpiresIn(): string { - return this.configService.get('API_TOKEN_EXPIRES_IN') ?? '1000y'; - } - getFrontAuthCallbackUrl(): string { return ( this.configService.get('FRONT_AUTH_CALLBACK_URL') ?? - this.getFrontBaseUrl() + '/verify' - ); - } - - isMessagingProviderGmailEnabled(): boolean { - return ( - this.configService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') ?? - false - ); - } - - isCalendarProviderGoogleEnabled(): boolean { - return ( - this.configService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') ?? - false - ); - } - - getMessagingProviderGmailCallbackUrl(): string | undefined { - return this.configService.get( - 'MESSAGING_PROVIDER_GMAIL_CALLBACK_URL', - ); - } - - getAuthGoogleAPIsCallbackUrl(): string | undefined { - return this.configService.get('AUTH_GOOGLE_APIS_CALLBACK_URL'); - } - - isAuthGoogleEnabled(): boolean { - return this.configService.get('AUTH_GOOGLE_ENABLED') ?? false; - } - - getAuthGoogleClientId(): string | undefined { - return this.configService.get('AUTH_GOOGLE_CLIENT_ID'); - } - - getAuthGoogleClientSecret(): string | undefined { - return this.configService.get('AUTH_GOOGLE_CLIENT_SECRET'); - } - - getAuthGoogleCallbackUrl(): string | undefined { - return this.configService.get('AUTH_GOOGLE_CALLBACK_URL'); - } - - getStorageDriverType(): StorageDriverType { - return ( - this.configService.get('STORAGE_TYPE') ?? - StorageDriverType.Local - ); - } - - getMessageQueueDriverType(): MessageQueueDriverType { - return ( - this.configService.get('MESSAGE_QUEUE_TYPE') ?? - MessageQueueDriverType.Sync - ); - } - - getStorageS3Region(): AwsRegion | undefined { - return this.configService.get('STORAGE_S3_REGION'); - } - - getStorageS3Name(): string | undefined { - return this.configService.get('STORAGE_S3_NAME'); - } - - getStorageS3Endpoint(): string | undefined { - return this.configService.get('STORAGE_S3_ENDPOINT'); - } - - getStorageLocalPath(): string { - return ( - this.configService.get('STORAGE_LOCAL_PATH') ?? '.local-storage' - ); - } - - getEmailFromAddress(): string { - return ( - this.configService.get('EMAIL_FROM_ADDRESS') ?? - 'noreply@yourdomain.com' - ); - } - - getEmailSystemAddress(): string { - return ( - this.configService.get('EMAIL_SYSTEM_ADDRESS') ?? - 'system@yourdomain.com' - ); - } - - getEmailFromName(): string { - return ( - this.configService.get('EMAIL_FROM_NAME') ?? - 'John from YourDomain' - ); - } - - getEmailDriver(): EmailDriver { - return ( - this.configService.get('EMAIL_DRIVER') ?? EmailDriver.Logger - ); - } - - getEmailHost(): string | undefined { - return this.configService.get('EMAIL_SMTP_HOST'); - } - - getEmailPort(): number | undefined { - return this.configService.get('EMAIL_SMTP_PORT'); - } - - getEmailUser(): string | undefined { - return this.configService.get('EMAIL_SMTP_USER'); - } - - getEmailPassword(): string | undefined { - return this.configService.get('EMAIL_SMTP_PASSWORD'); - } - - getSupportDriver(): string { - return ( - this.configService.get('SUPPORT_DRIVER') ?? SupportDriver.None - ); - } - - getSupportFrontChatId(): string | undefined { - return this.configService.get('SUPPORT_FRONT_CHAT_ID'); - } - - getSupportFrontHMACKey(): string | undefined { - return this.configService.get('SUPPORT_FRONT_HMAC_KEY'); - } - - getLoggerDriverType(): LoggerDriverType { - return ( - this.configService.get('LOGGER_DRIVER') ?? - LoggerDriverType.Console + this.get('FRONT_BASE_URL') + '/verify' ); } + // TODO: check because it isn't called getLoggerIsBufferEnabled(): boolean | undefined { return this.configService.get('LOGGER_IS_BUFFER_ENABLED') ?? true; } - - getExceptionHandlerDriverType(): ExceptionHandlerDriver { - return ( - this.configService.get( - 'EXCEPTION_HANDLER_DRIVER', - ) ?? ExceptionHandlerDriver.Console - ); - } - - getLogLevels(): LogLevel[] { - return ( - this.configService.get('LOG_LEVELS') ?? [ - 'log', - 'error', - 'warn', - ] - ); - } - - getSentryDSN(): string | undefined { - return this.configService.get('SENTRY_DSN'); - } - - getDemoWorkspaceIds(): string[] { - return this.configService.get('DEMO_WORKSPACE_IDS') ?? []; - } - - getOpenRouterApiKey(): string | undefined { - return this.configService.get('OPENROUTER_API_KEY'); - } - - getPasswordResetTokenExpiresIn(): string { - return ( - this.configService.get('PASSWORD_RESET_TOKEN_EXPIRES_IN') ?? '5m' - ); - } - - getInactiveDaysBeforeEmail(): number | undefined { - return this.configService.get( - 'WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION', - ); - } - - getInactiveDaysBeforeDelete(): number | undefined { - return this.configService.get( - 'WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION', - ); - } - - isSignUpDisabled(): boolean { - return this.configService.get('IS_SIGN_UP_DISABLED') ?? false; - } - - getApiRateLimitingTtl(): number { - return this.configService.get('API_RATE_LIMITING_TTL') ?? 100; - } - - getApiRateLimitingLimit(): number { - return this.configService.get('API_RATE_LIMITING_LIMIT') ?? 500; - } - - getMutationMaximumRecordAffected(): number { - return ( - this.configService.get('MUTATION_MAXIMUM_RECORD_AFFECTED') ?? 100 - ); - } - - getCacheStorageType(): string { - return this.configService.get('CACHE_STORAGE_TYPE') ?? 'memory'; - } - - getCacheStorageTtl(): number { - return this.configService.get('CACHE_STORAGE_TTL') ?? 3600 * 24 * 7; - } } diff --git a/packages/twenty-server/src/integrations/exception-handler/exception-handler.module-factory.ts b/packages/twenty-server/src/integrations/exception-handler/exception-handler.module-factory.ts index c8d601d22..0b08aebd3 100644 --- a/packages/twenty-server/src/integrations/exception-handler/exception-handler.module-factory.ts +++ b/packages/twenty-server/src/integrations/exception-handler/exception-handler.module-factory.ts @@ -13,7 +13,7 @@ export const exceptionHandlerModuleFactory = async ( environmentService: EnvironmentService, adapterHost: HttpAdapterHost, ): Promise => { - const driverType = environmentService.getExceptionHandlerDriverType(); + const driverType = environmentService.get('EXCEPTION_HANDLER_DRIVER'); switch (driverType) { case ExceptionHandlerDriver.Console: { @@ -25,9 +25,9 @@ export const exceptionHandlerModuleFactory = async ( return { type: ExceptionHandlerDriver.Sentry, options: { - dsn: environmentService.getSentryDSN() ?? '', + dsn: environmentService.get('SENTRY_DSN') ?? '', serverInstance: adapterHost.httpAdapter?.getInstance(), - debug: environmentService.isDebugMode(), + debug: environmentService.get('DEBUG_MODE'), }, }; } diff --git a/packages/twenty-server/src/integrations/file-storage/file-storage.module-factory.ts b/packages/twenty-server/src/integrations/file-storage/file-storage.module-factory.ts index c4a8e266e..22c1ee1af 100644 --- a/packages/twenty-server/src/integrations/file-storage/file-storage.module-factory.ts +++ b/packages/twenty-server/src/integrations/file-storage/file-storage.module-factory.ts @@ -14,11 +14,11 @@ import { export const fileStorageModuleFactory = async ( environmentService: EnvironmentService, ): Promise => { - const driverType = environmentService.getStorageDriverType(); + const driverType = environmentService.get('STORAGE_TYPE'); switch (driverType) { case StorageDriverType.Local: { - const storagePath = environmentService.getStorageLocalPath(); + const storagePath = environmentService.get('STORAGE_LOCAL_PATH'); return { type: StorageDriverType.Local, @@ -28,9 +28,9 @@ export const fileStorageModuleFactory = async ( }; } case StorageDriverType.S3: { - const bucketName = environmentService.getStorageS3Name(); - const endpoint = environmentService.getStorageS3Endpoint(); - const region = environmentService.getStorageS3Region(); + const bucketName = environmentService.get('STORAGE_S3_NAME'); + const endpoint = environmentService.get('STORAGE_S3_ENDPOINT'); + const region = environmentService.get('STORAGE_S3_REGION'); return { type: StorageDriverType.S3, diff --git a/packages/twenty-server/src/integrations/logger/logger.module-factory.ts b/packages/twenty-server/src/integrations/logger/logger.module-factory.ts index 2e4ec8cd0..bed4bdc3b 100644 --- a/packages/twenty-server/src/integrations/logger/logger.module-factory.ts +++ b/packages/twenty-server/src/integrations/logger/logger.module-factory.ts @@ -12,8 +12,8 @@ import { export const loggerModuleFactory = async ( environmentService: EnvironmentService, ): Promise => { - const driverType = environmentService.getLoggerDriverType(); - const logLevels = environmentService.getLogLevels(); + const driverType = environmentService.get('LOGGER_DRIVER'); + const logLevels = environmentService.get('LOG_LEVELS'); switch (driverType) { case LoggerDriverType.Console: { diff --git a/packages/twenty-server/src/integrations/message-queue/message-queue.module-factory.ts b/packages/twenty-server/src/integrations/message-queue/message-queue.module-factory.ts index 689368342..8829c1dab 100644 --- a/packages/twenty-server/src/integrations/message-queue/message-queue.module-factory.ts +++ b/packages/twenty-server/src/integrations/message-queue/message-queue.module-factory.ts @@ -12,7 +12,7 @@ import { export const messageQueueModuleFactory = async ( environmentService: EnvironmentService, ): Promise => { - const driverType = environmentService.getMessageQueueDriverType(); + const driverType = environmentService.get('MESSAGE_QUEUE_TYPE'); switch (driverType) { case MessageQueueDriverType.Sync: { @@ -22,7 +22,7 @@ export const messageQueueModuleFactory = async ( }; } case MessageQueueDriverType.PgBoss: { - const connectionString = environmentService.getPGDatabaseUrl(); + const connectionString = environmentService.get('PG_DATABASE_URL'); return { type: MessageQueueDriverType.PgBoss, @@ -32,8 +32,8 @@ export const messageQueueModuleFactory = async ( }; } case MessageQueueDriverType.BullMQ: { - const host = environmentService.getRedisHost(); - const port = environmentService.getRedisPort(); + const host = environmentService.get('REDIS_HOST'); + const port = environmentService.get('REDIS_PORT'); return { type: MessageQueueDriverType.BullMQ, diff --git a/packages/twenty-server/src/main.ts b/packages/twenty-server/src/main.ts index 96e12cb40..2b1eb9e65 100644 --- a/packages/twenty-server/src/main.ts +++ b/packages/twenty-server/src/main.ts @@ -53,7 +53,7 @@ const bootstrap = async () => { }), ); - await app.listen(app.get(EnvironmentService).getPort()); + await app.listen(app.get(EnvironmentService).get('PORT')); }; bootstrap(); diff --git a/packages/twenty-server/src/metadata/metadata.module-factory.ts b/packages/twenty-server/src/metadata/metadata.module-factory.ts index 7dd0dafed..428626040 100644 --- a/packages/twenty-server/src/metadata/metadata.module-factory.ts +++ b/packages/twenty-server/src/metadata/metadata.module-factory.ts @@ -26,8 +26,8 @@ export const metadataModuleFactory = async ( resolvers: { JSON: GraphQLJSON }, plugins: [ useThrottler({ - ttl: environmentService.getApiRateLimitingTtl(), - limit: environmentService.getApiRateLimitingLimit(), + ttl: environmentService.get('API_RATE_LIMITING_TTL'), + limit: environmentService.get('API_RATE_LIMITING_LIMIT'), identifyFn: (context) => { return context.user?.id ?? context.req.ip ?? 'anonymous'; }, @@ -39,7 +39,7 @@ export const metadataModuleFactory = async ( path: '/metadata', }; - if (environmentService.isDebugMode()) { + if (environmentService.get('DEBUG_MODE')) { config.renderGraphiQL = () => { return renderApolloPlayground({ path: 'metadata' }); }; diff --git a/packages/twenty-server/src/workspace/calendar-and-messaging/services/google-apis-refresh-access-token.service.ts b/packages/twenty-server/src/workspace/calendar-and-messaging/services/google-apis-refresh-access-token.service.ts index b30c1ded7..d00c433ef 100644 --- a/packages/twenty-server/src/workspace/calendar-and-messaging/services/google-apis-refresh-access-token.service.ts +++ b/packages/twenty-server/src/workspace/calendar-and-messaging/services/google-apis-refresh-access-token.service.ts @@ -48,8 +48,8 @@ export class GoogleAPIsRefreshAccessTokenService { const response = await axios.post( 'https://oauth2.googleapis.com/token', { - client_id: this.environmentService.getAuthGoogleClientId(), - client_secret: this.environmentService.getAuthGoogleClientSecret(), + client_id: this.environmentService.get('AUTH_GOOGLE_CLIENT_ID'), + client_secret: this.environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'), refresh_token: refreshToken, grant_type: 'refresh_token', }, diff --git a/packages/twenty-server/src/workspace/calendar/services/providers/google-calendar/google-calendar.provider.ts b/packages/twenty-server/src/workspace/calendar/services/providers/google-calendar/google-calendar.provider.ts index 15b157a43..d93f7e9f8 100644 --- a/packages/twenty-server/src/workspace/calendar/services/providers/google-calendar/google-calendar.provider.ts +++ b/packages/twenty-server/src/workspace/calendar/services/providers/google-calendar/google-calendar.provider.ts @@ -23,10 +23,12 @@ export class GoogleCalendarClientProvider { } private async getOAuth2Client(refreshToken: string): Promise { - const googleCalendarClientId = - this.environmentService.getAuthGoogleClientId(); - const googleCalendarClientSecret = - this.environmentService.getAuthGoogleClientSecret(); + const googleCalendarClientId = this.environmentService.get( + 'AUTH_GOOGLE_CLIENT_ID', + ); + const googleCalendarClientSecret = this.environmentService.get( + 'AUTH_GOOGLE_CLIENT_SECRET', + ); const oAuth2Client = new google.auth.OAuth2( googleCalendarClientId, diff --git a/packages/twenty-server/src/workspace/messaging/services/providers/gmail/gmail-client.provider.ts b/packages/twenty-server/src/workspace/messaging/services/providers/gmail/gmail-client.provider.ts index 2573df435..fa74f383a 100644 --- a/packages/twenty-server/src/workspace/messaging/services/providers/gmail/gmail-client.provider.ts +++ b/packages/twenty-server/src/workspace/messaging/services/providers/gmail/gmail-client.provider.ts @@ -21,9 +21,9 @@ export class GmailClientProvider { } private async getOAuth2Client(refreshToken: string): Promise { - const gmailClientId = this.environmentService.getAuthGoogleClientId(); + const gmailClientId = this.environmentService.get('AUTH_GOOGLE_CLIENT_ID'); const gmailClientSecret = - this.environmentService.getAuthGoogleClientSecret(); + this.environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'); const oAuth2Client = new google.auth.OAuth2( gmailClientId, diff --git a/packages/twenty-server/src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job.ts b/packages/twenty-server/src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job.ts index 026d16c8e..6a874ea0e 100644 --- a/packages/twenty-server/src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job.ts +++ b/packages/twenty-server/src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job.ts @@ -45,9 +45,9 @@ export class CleanInactiveWorkspaceJob private readonly environmentService: EnvironmentService, ) { this.inactiveDaysBeforeDelete = - this.environmentService.getInactiveDaysBeforeDelete(); + this.environmentService.get('WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION'); this.inactiveDaysBeforeEmail = - this.environmentService.getInactiveDaysBeforeEmail(); + this.environmentService.get('WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION'); } async getMostRecentUpdatedAt( @@ -133,8 +133,8 @@ export class CleanInactiveWorkspaceJob this.emailService.send({ to: workspaceMember.email, - bcc: this.environmentService.getEmailSystemAddress(), - from: `${this.environmentService.getEmailFromName()} <${this.environmentService.getEmailFromAddress()}>`, + bcc: this.environmentService.get('EMAIL_SYSTEM_ADDRESS'), + from: `${this.environmentService.get('EMAIL_FROM_NAME')} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`, subject: 'Action Needed to Prevent Workspace Deletion', html, text, @@ -179,8 +179,8 @@ export class CleanInactiveWorkspaceJob }); await this.emailService.send({ - to: this.environmentService.getEmailSystemAddress(), - from: `${this.environmentService.getEmailFromName()} <${this.environmentService.getEmailFromAddress()}>`, + to: this.environmentService.get('EMAIL_SYSTEM_ADDRESS'), + from: `${this.environmentService.get('EMAIL_FROM_NAME')} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`, subject: 'Action Needed to Delete Workspaces', html, text, diff --git a/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts b/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts index a2b8eabdc..68ba04080 100644 --- a/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts +++ b/packages/twenty-server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts @@ -307,7 +307,7 @@ export class WorkspaceQueryRunnerService { ): Promise { const { workspaceId, objectMetadataItem } = options; const maximumRecordAffected = - this.environmentService.getMutationMaximumRecordAffected(); + this.environmentService.get('MUTATION_MAXIMUM_RECORD_AFFECTED'); const query = await this.workspaceQueryBuilderFactory.updateMany(args, { ...options, atMost: maximumRecordAffected, @@ -339,7 +339,7 @@ export class WorkspaceQueryRunnerService { ): Promise { const { workspaceId, objectMetadataItem } = options; const maximumRecordAffected = - this.environmentService.getMutationMaximumRecordAffected(); + this.environmentService.get('MUTATION_MAXIMUM_RECORD_AFFECTED'); const query = await this.workspaceQueryBuilderFactory.deleteMany(args, { ...options, atMost: maximumRecordAffected,