Refacto environment service (#4473)

* Refacto environment service

* Remove environment variable type
This commit is contained in:
Félix Malfait
2024-03-14 11:51:19 +01:00
committed by GitHub
parent 3caf860848
commit fd06d52a13
42 changed files with 320 additions and 480 deletions

View File

@ -23,12 +23,13 @@ export class AnalyticsService {
workspace: Workspace | undefined, workspace: Workspace | undefined,
request: Request, request: Request,
) { ) {
if (!this.environmentService.isTelemetryEnabled()) { if (!this.environmentService.get('TELEMETRY_ENABLED')) {
return { success: true }; return { success: true };
} }
const anonymizationEnabled = const anonymizationEnabled = this.environmentService.get(
this.environmentService.isTelemetryAnonymizationEnabled(); 'TELEMETRY_ANONYMIZATION_ENABLED',
);
const data = { const data = {
type: createEventInput.type, type: createEventInput.type,

View File

@ -47,7 +47,7 @@ export class ApiRestQueryBuilderFactory {
if (!objectMetadataItems.length) { if (!objectMetadataItems.length) {
throw new BadRequestException( 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`,
); );
} }

View File

@ -30,9 +30,9 @@ import { AuthService } from './services/auth.service';
const jwtModule = JwtModule.registerAsync({ const jwtModule = JwtModule.registerAsync({
useFactory: async (environmentService: EnvironmentService) => { useFactory: async (environmentService: EnvironmentService) => {
return { return {
secret: environmentService.getAccessTokenSecret(), secret: environmentService.get('ACCESS_TOKEN_SECRET'),
signOptions: { signOptions: {
expiresIn: environmentService.getAccessTokenExpiresIn(), expiresIn: environmentService.get('ACCESS_TOKEN_EXPIRES_IN'),
}, },
}; };
}, },

View File

@ -37,7 +37,7 @@ export class GoogleAPIsAuthController {
const { workspaceMemberId, workspaceId } = const { workspaceMemberId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken); await this.tokenService.verifyTransientToken(transientToken);
const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS');
if (demoWorkspaceIds.includes(workspaceId)) { if (demoWorkspaceIds.includes(workspaceId)) {
throw new Error('Cannot connect Google account to demo workspace'); throw new Error('Cannot connect Google account to demo workspace');
@ -57,7 +57,7 @@ export class GoogleAPIsAuthController {
}); });
return res.redirect( return res.redirect(
`${this.environmentService.getFrontBaseUrl()}/settings/accounts`, `${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`,
); );
} }
} }

View File

@ -37,7 +37,7 @@ export class GoogleGmailAuthController {
const { workspaceMemberId, workspaceId } = const { workspaceMemberId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken); await this.tokenService.verifyTransientToken(transientToken);
const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS');
if (demoWorkspaceIds.includes(workspaceId)) { if (demoWorkspaceIds.includes(workspaceId)) {
throw new Error('Cannot connect Gmail account to demo workspace'); throw new Error('Cannot connect Gmail account to demo workspace');
@ -58,7 +58,7 @@ export class GoogleGmailAuthController {
}); });
return res.redirect( return res.redirect(
`${this.environmentService.getFrontBaseUrl()}/settings/accounts`, `${this.environmentService.get('FRONT_BASE_URL')}/settings/accounts`,
); );
} }
} }

View File

@ -11,8 +11,8 @@ export class GoogleAPIsProviderEnabledGuard implements CanActivate {
canActivate(): boolean | Promise<boolean> | Observable<boolean> { canActivate(): boolean | Promise<boolean> | Observable<boolean> {
if ( if (
!this.environmentService.isMessagingProviderGmailEnabled() && !this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') &&
!this.environmentService.isCalendarProviderGoogleEnabled() !this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED')
) { ) {
throw new NotFoundException('Google apis auth is not enabled'); throw new NotFoundException('Google apis auth is not enabled');
} }

View File

@ -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<boolean> | Observable<boolean> {
if (!this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
throw new NotFoundException('Gmail auth is not enabled');
}
new GoogleAPIsStrategy(this.environmentService);
return true;
}
}

View File

@ -10,7 +10,7 @@ export class GoogleProviderEnabledGuard implements CanActivate {
constructor(private readonly environmentService: EnvironmentService) {} constructor(private readonly environmentService: EnvironmentService) {}
canActivate(): boolean | Promise<boolean> | Observable<boolean> { canActivate(): boolean | Promise<boolean> | Observable<boolean> {
if (!this.environmentService.isAuthGoogleEnabled()) { if (!this.environmentService.get('AUTH_GOOGLE_ENABLED')) {
throw new NotFoundException('Google auth is not enabled'); throw new NotFoundException('Google auth is not enabled');
} }

View File

@ -194,7 +194,7 @@ export class AuthService {
const emailTemplate = PasswordUpdateNotifyEmail({ const emailTemplate = PasswordUpdateNotifyEmail({
userName: `${user.firstName} ${user.lastName}`, userName: `${user.firstName} ${user.lastName}`,
email: user.email, email: user.email,
link: this.environmentService.getFrontBaseUrl(), link: this.environmentService.get('FRONT_BASE_URL'),
}); });
const html = render(emailTemplate, { const html = render(emailTemplate, {
@ -205,7 +205,7 @@ export class AuthService {
}); });
this.emailService.send({ 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, to: user.email,
subject: 'Your Password Has Been Successfully Changed', subject: 'Your Password Has Been Successfully Changed',
text, text,

View File

@ -88,7 +88,7 @@ export class GoogleAPIsService {
], ],
); );
if (this.environmentService.isMessagingProviderGmailEnabled()) { if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
await manager.query( await manager.query(
`INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type") VALUES ($1, $2, $3, $4)`, `INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type") VALUES ($1, $2, $3, $4)`,
['share_everything', handle, connectedAccountId, 'email'], ['share_everything', handle, connectedAccountId, 'email'],
@ -96,7 +96,7 @@ export class GoogleAPIsService {
} }
if ( if (
this.environmentService.isCalendarProviderGoogleEnabled() && this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') &&
IsCalendarEnabled IsCalendarEnabled
) { ) {
await manager.query( 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<GmailFullSyncJobData>( await this.messageQueueService.add<GmailFullSyncJobData>(
GmailFullSyncJob.name, GmailFullSyncJob.name,
{ {
@ -120,7 +120,7 @@ export class GoogleAPIsService {
} }
if ( if (
this.environmentService.isCalendarProviderGoogleEnabled() && this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED') &&
IsCalendarEnabled IsCalendarEnabled
) { ) {
await this.calendarQueueService.add<GoogleCalendarFullSyncJobData>( await this.calendarQueueService.add<GoogleCalendarFullSyncJobData>(

View File

@ -190,7 +190,7 @@ export class SignUpService {
} }
assert( assert(
!this.environmentService.isSignUpDisabled(), !this.environmentService.get('IS_SIGN_UP_DISABLED'),
'Sign up is disabled', 'Sign up is disabled',
ForbiddenException, ForbiddenException,
); );

View File

@ -63,7 +63,7 @@ export class TokenService {
userId: string, userId: string,
workspaceId?: string, workspaceId?: string,
): Promise<AuthToken> { ): Promise<AuthToken> {
const expiresIn = this.environmentService.getAccessTokenExpiresIn(); const expiresIn = this.environmentService.get('ACCESS_TOKEN_EXPIRES_IN');
assert(expiresIn, '', InternalServerErrorException); assert(expiresIn, '', InternalServerErrorException);
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
@ -93,8 +93,8 @@ export class TokenService {
} }
async generateRefreshToken(userId: string): Promise<AuthToken> { async generateRefreshToken(userId: string): Promise<AuthToken> {
const secret = this.environmentService.getRefreshTokenSecret(); const secret = this.environmentService.get('REFRESH_TOKEN_SECRET');
const expiresIn = this.environmentService.getRefreshTokenExpiresIn(); const expiresIn = this.environmentService.get('REFRESH_TOKEN_EXPIRES_IN');
assert(expiresIn, '', InternalServerErrorException); assert(expiresIn, '', InternalServerErrorException);
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
@ -124,8 +124,8 @@ export class TokenService {
} }
async generateLoginToken(email: string): Promise<AuthToken> { async generateLoginToken(email: string): Promise<AuthToken> {
const secret = this.environmentService.getLoginTokenSecret(); const secret = this.environmentService.get('LOGIN_TOKEN_SECRET');
const expiresIn = this.environmentService.getLoginTokenExpiresIn(); const expiresIn = this.environmentService.get('LOGIN_TOKEN_EXPIRES_IN');
assert(expiresIn, '', InternalServerErrorException); assert(expiresIn, '', InternalServerErrorException);
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
@ -146,8 +146,8 @@ export class TokenService {
workspaceMemberId: string, workspaceMemberId: string,
workspaceId: string, workspaceId: string,
): Promise<AuthToken> { ): Promise<AuthToken> {
const secret = this.environmentService.getLoginTokenSecret(); const secret = this.environmentService.get('LOGIN_TOKEN_SECRET');
const expiresIn = this.environmentService.getTransientTokenExpiresIn(); const expiresIn = this.environmentService.get('SHORT_TERM_TOKEN_EXPIRES_IN');
assert(expiresIn, '', InternalServerErrorException); assert(expiresIn, '', InternalServerErrorException);
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
@ -176,7 +176,7 @@ export class TokenService {
const jwtPayload = { const jwtPayload = {
sub: workspaceId, sub: workspaceId,
}; };
const secret = this.environmentService.getAccessTokenSecret(); const secret = this.environmentService.get('ACCESS_TOKEN_SECRET');
let expiresIn: string | number; let expiresIn: string | number;
if (expiresAt) { if (expiresAt) {
@ -184,7 +184,7 @@ export class TokenService {
(new Date(expiresAt).getTime() - new Date().getTime()) / 1000, (new Date(expiresAt).getTime() - new Date().getTime()) / 1000,
); );
} else { } else {
expiresIn = this.environmentService.getApiTokenExpiresIn(); expiresIn = this.environmentService.get('API_TOKEN_EXPIRES_IN');
} }
const token = this.jwtService.sign(jwtPayload, { const token = this.jwtService.sign(jwtPayload, {
secret, secret,
@ -209,7 +209,7 @@ export class TokenService {
} }
const decoded = await this.verifyJwt( const decoded = await this.verifyJwt(
token, token,
this.environmentService.getAccessTokenSecret(), this.environmentService.get('ACCESS_TOKEN_SECRET'),
); );
const { user, workspace } = await this.jwtStrategy.validate( const { user, workspace } = await this.jwtStrategy.validate(
@ -220,7 +220,7 @@ export class TokenService {
} }
async verifyLoginToken(loginToken: string): Promise<string> { async verifyLoginToken(loginToken: string): Promise<string> {
const loginTokenSecret = this.environmentService.getLoginTokenSecret(); const loginTokenSecret = this.environmentService.get('LOGIN_TOKEN_SECRET');
const payload = await this.verifyJwt(loginToken, loginTokenSecret); const payload = await this.verifyJwt(loginToken, loginTokenSecret);
@ -231,7 +231,7 @@ export class TokenService {
workspaceMemberId: string; workspaceMemberId: string;
workspaceId: string; workspaceId: string;
}> { }> {
const transientTokenSecret = this.environmentService.getLoginTokenSecret(); const transientTokenSecret = this.environmentService.get('LOGIN_TOKEN_SECRET');
const payload = await this.verifyJwt(transientToken, transientTokenSecret); const payload = await this.verifyJwt(transientToken, transientTokenSecret);
@ -281,8 +281,8 @@ export class TokenService {
} }
async verifyRefreshToken(refreshToken: string) { async verifyRefreshToken(refreshToken: string) {
const secret = this.environmentService.getRefreshTokenSecret(); const secret = this.environmentService.get('REFRESH_TOKEN_SECRET');
const coolDown = this.environmentService.getRefreshTokenCoolDown(); const coolDown = this.environmentService.get('REFRESH_TOKEN_COOL_DOWN');
const jwtPayload = await this.verifyJwt(refreshToken, secret); const jwtPayload = await this.verifyJwt(refreshToken, secret);
assert( assert(
@ -382,7 +382,7 @@ export class TokenService {
assert(user, 'User not found', NotFoundException); assert(user, 'User not found', NotFoundException);
const expiresIn = this.environmentService.getPasswordResetTokenExpiresIn(); const expiresIn = this.environmentService.get('PASSWORD_RESET_TOKEN_EXPIRES_IN');
assert( assert(
expiresIn, expiresIn,
@ -439,7 +439,7 @@ export class TokenService {
assert(user, 'User not found', NotFoundException); 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 resetLink = `${frontBaseURL}/reset-password/${resetToken.passwordResetToken}`;
const emailData = { const emailData = {
@ -465,7 +465,7 @@ export class TokenService {
}); });
this.emailService.send({ 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, to: email,
subject: 'Action Needed to Reset Password', subject: 'Action Needed to Reset Password',
text, text,

View File

@ -27,20 +27,20 @@ export class GoogleAPIsStrategy extends PassportStrategy(
constructor(environmentService: EnvironmentService) { constructor(environmentService: EnvironmentService) {
const scope = ['email', 'profile']; const scope = ['email', 'profile'];
if (environmentService.isMessagingProviderGmailEnabled()) { if (environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
scope.push('https://www.googleapis.com/auth/gmail.readonly'); 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'); scope.push('https://www.googleapis.com/auth/calendar');
} }
super({ super({
clientID: environmentService.getAuthGoogleClientId(), clientID: environmentService.get('AUTH_GOOGLE_CLIENT_ID'),
clientSecret: environmentService.getAuthGoogleClientSecret(), clientSecret: environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'),
callbackURL: environmentService.isCalendarProviderGoogleEnabled() callbackURL: environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED')
? environmentService.getAuthGoogleAPIsCallbackUrl() ? environmentService.get('AUTH_GOOGLE_APIS_CALLBACK_URL')
: environmentService.getMessagingProviderGmailCallbackUrl(), : environmentService.get('MESSAGING_PROVIDER_GMAIL_CALLBACK_URL'),
scope, scope,
passReqToCallback: true, passReqToCallback: true,
}); });

View File

@ -20,9 +20,9 @@ export type GoogleRequest = Request & {
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(environmentService: EnvironmentService) { constructor(environmentService: EnvironmentService) {
super({ super({
clientID: environmentService.getAuthGoogleClientId(), clientID: environmentService.get('AUTH_GOOGLE_CLIENT_ID'),
clientSecret: environmentService.getAuthGoogleClientSecret(), clientSecret: environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'),
callbackURL: environmentService.getAuthGoogleCallbackUrl(), callbackURL: environmentService.get('AUTH_GOOGLE_CALLBACK_URL'),
scope: ['email', 'profile'], scope: ['email', 'profile'],
passReqToCallback: true, passReqToCallback: true,
}); });

View File

@ -33,7 +33,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
super({ super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false, ignoreExpiration: false,
secretOrKey: environmentService.getAccessTokenSecret(), secretOrKey: environmentService.get('ACCESS_TOKEN_SECRET'),
}); });
} }

View File

@ -42,7 +42,7 @@ export class BillingService {
getProductStripeId(product: AvailableProduct) { getProductStripeId(product: AvailableProduct) {
if (product === AvailableProduct.BasePlan) { 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( async getBillingSubscriptionItem(
workspaceId: string, workspaceId: string,
stripeProductId = this.environmentService.getBillingStripeBasePlanProductId(), stripeProductId = this.environmentService.get('BILLING_STRIPE_BASE_PLAN_PRODUCT_ID'),
) { ) {
const billingSubscription = await this.getCurrentBillingSubscription({ const billingSubscription = await this.getCurrentBillingSubscription({
workspaceId, workspaceId,
@ -134,7 +134,7 @@ export class BillingService {
where: { workspaceId }, where: { workspaceId },
}); });
const frontBaseUrl = this.environmentService.getFrontBaseUrl(); const frontBaseUrl = this.environmentService.get('FRONT_BASE_URL');
const returnUrl = returnUrlPath const returnUrl = returnUrlPath
? frontBaseUrl + returnUrlPath ? frontBaseUrl + returnUrlPath
: frontBaseUrl; : frontBaseUrl;
@ -154,7 +154,7 @@ export class BillingService {
priceId: string, priceId: string,
successUrlPath?: string, successUrlPath?: string,
): Promise<string> { ): Promise<string> {
const frontBaseUrl = this.environmentService.getFrontBaseUrl(); const frontBaseUrl = this.environmentService.get('FRONT_BASE_URL');
const successUrl = successUrlPath const successUrl = successUrlPath
? frontBaseUrl + successUrlPath ? frontBaseUrl + successUrlPath
: frontBaseUrl; : frontBaseUrl;

View File

@ -12,14 +12,14 @@ export class StripeService {
constructor(private readonly environmentService: EnvironmentService) { constructor(private readonly environmentService: EnvironmentService) {
this.stripe = new Stripe( this.stripe = new Stripe(
this.environmentService.getBillingStripeApiKey(), this.environmentService.get('BILLING_STRIPE_API_KEY'),
{}, {},
); );
} }
constructEventFromPayload(signature: string, payload: Buffer) { constructEventFromPayload(signature: string, payload: Buffer) {
const webhookSecret = const webhookSecret =
this.environmentService.getBillingStripeWebhookSecret(); this.environmentService.get('BILLING_STRIPE_WEBHOOK_SECRET');
return this.stripe.webhooks.constructEvent( return this.stripe.webhooks.constructEvent(
payload, payload,
@ -48,7 +48,7 @@ export class StripeService {
): Promise<Stripe.BillingPortal.Session> { ): Promise<Stripe.BillingPortal.Session> {
return await this.stripe.billingPortal.sessions.create({ return await this.stripe.billingPortal.sessions.create({
customer: stripeCustomerId, 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, workspaceId: user.defaultWorkspace.id,
}, },
trial_period_days: trial_period_days:
this.environmentService.getBillingFreeTrialDurationInDays(), this.environmentService.get('BILLING_FREE_TRIAL_DURATION_IN_DAYS'),
}, },
automatic_tax: { enabled: true }, automatic_tax: { enabled: true },
tax_id_collection: { enabled: true }, tax_id_collection: { enabled: true },

View File

@ -12,30 +12,34 @@ export class ClientConfigResolver {
async clientConfig(): Promise<ClientConfig> { async clientConfig(): Promise<ClientConfig> {
const clientConfig: ClientConfig = { const clientConfig: ClientConfig = {
authProviders: { authProviders: {
google: this.environmentService.isAuthGoogleEnabled(), google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
magicLink: false, magicLink: false,
password: true, password: true,
}, },
telemetry: { telemetry: {
enabled: this.environmentService.isTelemetryEnabled(), enabled: this.environmentService.get('TELEMETRY_ENABLED'),
anonymizationEnabled: anonymizationEnabled: this.environmentService.get(
this.environmentService.isTelemetryAnonymizationEnabled(), 'TELEMETRY_ANONYMIZATION_ENABLED',
),
}, },
billing: { billing: {
isBillingEnabled: this.environmentService.isBillingEnabled(), isBillingEnabled: this.environmentService.get('IS_BILLING_ENABLED'),
billingUrl: this.environmentService.getBillingUrl(), billingUrl: this.environmentService.get('BILLING_PLAN_REQUIRED_LINK'),
billingFreeTrialDurationInDays: billingFreeTrialDurationInDays: this.environmentService.get(
this.environmentService.getBillingFreeTrialDurationInDays(), 'BILLING_FREE_TRIAL_DURATION_IN_DAYS',
),
}, },
signInPrefilled: this.environmentService.isSignInPrefilled(), signInPrefilled: this.environmentService.get('SIGN_IN_PREFILLED'),
signUpDisabled: this.environmentService.isSignUpDisabled(), signUpDisabled: this.environmentService.get('IS_SIGN_UP_DISABLED'),
debugMode: this.environmentService.isDebugMode(), debugMode: this.environmentService.get('DEBUG_MODE'),
support: { support: {
supportDriver: this.environmentService.getSupportDriver(), supportDriver: this.environmentService.get('SUPPORT_DRIVER'),
supportFrontChatId: this.environmentService.getSupportFrontChatId(), supportFrontChatId: this.environmentService.get(
'SUPPORT_FRONT_CHAT_ID',
),
}, },
sentry: { sentry: {
dsn: this.environmentService.getSentryDSN(), dsn: this.environmentService.get('SENTRY_DSN'),
}, },
}; };

View File

@ -37,7 +37,7 @@ export class IntelligenceService {
'https://openrouter.ai/api/v1/chat/completions', 'https://openrouter.ai/api/v1/chat/completions',
{ {
headers: { headers: {
Authorization: `Bearer ${this.environmentService.getOpenRouterApiKey()}`, Authorization: `Bearer ${this.environmentService.get('OPENROUTER_API_KEY')}`,
'HTTP-Referer': `https://twenty.com`, 'HTTP-Referer': `https://twenty.com`,
'X-Title': `Twenty CRM`, 'X-Title': `Twenty CRM`,
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -76,10 +76,10 @@ export class UserResolver {
nullable: true, nullable: true,
}) })
supportUserHash(@Parent() parent: User): string | null { supportUserHash(@Parent() parent: User): string | null {
if (this.environmentService.getSupportDriver() !== SupportDriver.Front) { if (this.environmentService.get('SUPPORT_DRIVER') !== SupportDriver.Front) {
return null; return null;
} }
const key = this.environmentService.getSupportFrontHMACKey(); const key = this.environmentService.get('SUPPORT_FRONT_HMAC_KEY');
return getHMACKey(parent.email, key); return getHMACKey(parent.email, key);
} }
@ -111,7 +111,7 @@ export class UserResolver {
@Mutation(() => User) @Mutation(() => User)
async deleteUser(@AuthUser() { id: userId, defaultWorkspace }: User) { async deleteUser(@AuthUser() { id: userId, defaultWorkspace }: User) {
// Get the list of demo workspace IDs // Get the list of demo workspace IDs
const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS');
const currentUserWorkspaceId = defaultWorkspace.id; const currentUserWorkspaceId = defaultWorkspace.id;

View File

@ -91,7 +91,7 @@ export class WorkspaceResolver {
@Mutation(() => Workspace) @Mutation(() => Workspace)
async deleteCurrentWorkspace(@AuthWorkspace() { id }: 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 // Check if the id is in the list of demo workspaceIds
if (demoWorkspaceIds.includes(id)) { if (demoWorkspaceIds.includes(id)) {

View File

@ -19,14 +19,14 @@ export class DataSeedDemoWorkspaceService {
async seedDemo(): Promise<void> { async seedDemo(): Promise<void> {
try { try {
const dataSource = new DataSource({ const dataSource = new DataSource({
url: this.environmentService.getPGDatabaseUrl(), url: this.environmentService.get('PG_DATABASE_URL'),
type: 'postgres', type: 'postgres',
logging: true, logging: true,
schema: 'public', schema: 'public',
}); });
await dataSource.initialize(); await dataSource.initialize();
const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds(); const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS');
if (demoWorkspaceIds.length === 0) { if (demoWorkspaceIds.length === 0) {
throw new Error( throw new Error(

View File

@ -38,7 +38,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
async run(): Promise<void> { async run(): Promise<void> {
try { try {
const dataSource = new DataSource({ const dataSource = new DataSource({
url: this.environmentService.getPGDatabaseUrl(), url: this.environmentService.get('PG_DATABASE_URL'),
type: 'postgres', type: 'postgres',
logging: true, logging: true,
schema: 'core', schema: 'core',

View File

@ -20,7 +20,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
constructor(private readonly environmentService: EnvironmentService) { constructor(private readonly environmentService: EnvironmentService) {
this.mainDataSource = new DataSource({ this.mainDataSource = new DataSource({
url: environmentService.getPGDatabaseUrl(), url: environmentService.get('PG_DATABASE_URL'),
type: 'postgres', type: 'postgres',
logging: false, logging: false,
schema: 'core', schema: 'core',
@ -83,9 +83,9 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
const schema = dataSource.schema; const schema = dataSource.schema;
const workspaceDataSource = new DataSource({ const workspaceDataSource = new DataSource({
url: dataSource.url ?? this.environmentService.getPGDatabaseUrl(), url: dataSource.url ?? this.environmentService.get('PG_DATABASE_URL'),
type: 'postgres', type: 'postgres',
logging: this.environmentService.isDebugMode() logging: this.environmentService.get('DEBUG_MODE')
? ['query', 'error'] ? ['query', 'error']
: ['error'], : ['error'],
schema, schema,

View File

@ -46,11 +46,11 @@ export class GraphQLConfigService
) {} ) {}
createGqlOptions(): YogaDriverConfig { createGqlOptions(): YogaDriverConfig {
const isDebugMode = this.environmentService.isDebugMode(); const isDebugMode = this.environmentService.get('DEBUG_MODE');
const plugins = [ const plugins = [
useThrottler({ useThrottler({
ttl: this.environmentService.getApiRateLimitingTtl(), ttl: this.environmentService.get('API_RATE_LIMITING_TTL'),
limit: this.environmentService.getApiRateLimitingLimit(), limit: this.environmentService.get('API_RATE_LIMITING_LIMIT'),
identifyFn: (context) => { identifyFn: (context) => {
return context.user?.id ?? context.req.ip ?? 'anonymous'; return context.user?.id ?? context.req.ip ?? 'anonymous';
}, },

View File

@ -8,8 +8,8 @@ import { EnvironmentService } from 'src/integrations/environment/environment.ser
export const cacheStorageModuleFactory = ( export const cacheStorageModuleFactory = (
environmentService: EnvironmentService, environmentService: EnvironmentService,
): CacheModuleOptions => { ): CacheModuleOptions => {
const cacheStorageType = environmentService.getCacheStorageType(); const cacheStorageType = environmentService.get('CACHE_STORAGE_TYPE');
const cacheStorageTtl = environmentService.getCacheStorageTtl(); const cacheStorageTtl = environmentService.get('CACHE_STORAGE_TTL');
const cacheModuleOptions: CacheModuleOptions = { const cacheModuleOptions: CacheModuleOptions = {
isGlobal: true, isGlobal: true,
ttl: cacheStorageTtl * 1000, ttl: cacheStorageTtl * 1000,
@ -20,8 +20,8 @@ export const cacheStorageModuleFactory = (
return cacheModuleOptions; return cacheModuleOptions;
} }
case CacheStorageType.Redis: { case CacheStorageType.Redis: {
const host = environmentService.getRedisHost(); const host = environmentService.get('REDIS_HOST');
const port = environmentService.getRedisPort(); const port = environmentService.get('REDIS_PORT');
if (!(host && port)) { if (!(host && port)) {
throw new Error( throw new Error(

View File

@ -8,17 +8,17 @@ import { EnvironmentService } from 'src/integrations/environment/environment.ser
export const emailModuleFactory = ( export const emailModuleFactory = (
environmentService: EnvironmentService, environmentService: EnvironmentService,
): EmailModuleOptions => { ): EmailModuleOptions => {
const driver = environmentService.getEmailDriver(); const driver = environmentService.get('EMAIL_DRIVER');
switch (driver) { switch (driver) {
case EmailDriver.Logger: { case EmailDriver.Logger: {
return; return;
} }
case EmailDriver.Smtp: { case EmailDriver.Smtp: {
const host = environmentService.getEmailHost(); const host = environmentService.get('EMAIL_SMTP_HOST');
const port = environmentService.getEmailPort(); const port = environmentService.get('EMAIL_SMTP_PORT');
const user = environmentService.getEmailUser(); const user = environmentService.get('EMAIL_SMTP_USER');
const pass = environmentService.getEmailPassword(); const pass = environmentService.get('EMAIL_SMTP_PASSWORD');
if (!(host && port)) { if (!(host && port)) {
throw new Error( throw new Error(

View File

@ -10,8 +10,11 @@ import {
validateSync, validateSync,
IsBoolean, IsBoolean,
IsNumber, IsNumber,
IsDefined,
} from 'class-validator'; } from 'class-validator';
import { EmailDriver } from 'src/integrations/email/interfaces/email.interface';
import { assert } from 'src/utils/assert'; import { assert } from 'src/utils/assert';
import { CastToStringArray } from 'src/integrations/environment/decorators/cast-to-string-array.decorator'; import { CastToStringArray } from 'src/integrations/environment/decorators/cast-to-string-array.decorator';
import { ExceptionHandlerDriver } from 'src/integrations/exception-handler/interfaces'; import { ExceptionHandlerDriver } from 'src/integrations/exception-handler/interfaces';
@ -32,49 +35,49 @@ export class EnvironmentVariables {
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
DEBUG_MODE?: boolean; DEBUG_MODE: boolean;
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
SIGN_IN_PREFILLED?: boolean; SIGN_IN_PREFILLED: boolean;
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
IS_BILLING_ENABLED?: boolean; IS_BILLING_ENABLED: boolean;
@IsString() @IsString()
@ValidateIf((env) => env.IS_BILLING_ENABLED === true) @ValidateIf((env) => env.IS_BILLING_ENABLED === true)
BILLING_PLAN_REQUIRED_LINK?: string; BILLING_PLAN_REQUIRED_LINK: string;
@IsString() @IsString()
@ValidateIf((env) => env.IS_BILLING_ENABLED === true) @ValidateIf((env) => env.IS_BILLING_ENABLED === true)
BILLING_STRIPE_BASE_PLAN_PRODUCT_ID?: string; BILLING_STRIPE_BASE_PLAN_PRODUCT_ID: string;
@IsNumber() @IsNumber()
@CastToPositiveNumber() @CastToPositiveNumber()
@IsOptional() @IsOptional()
@ValidateIf((env) => env.IS_BILLING_ENABLED === true) @ValidateIf((env) => env.IS_BILLING_ENABLED === true)
BILLING_FREE_TRIAL_DURATION_IN_DAYS?: number; BILLING_FREE_TRIAL_DURATION_IN_DAYS: number;
@IsString() @IsString()
@ValidateIf((env) => env.IS_BILLING_ENABLED === true) @ValidateIf((env) => env.IS_BILLING_ENABLED === true)
BILLING_STRIPE_API_KEY?: string; BILLING_STRIPE_API_KEY: string;
@IsString() @IsString()
@ValidateIf((env) => env.IS_BILLING_ENABLED === true) @ValidateIf((env) => env.IS_BILLING_ENABLED === true)
BILLING_STRIPE_WEBHOOK_SECRET?: string; BILLING_STRIPE_WEBHOOK_SECRET: string;
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
TELEMETRY_ENABLED?: boolean; TELEMETRY_ENABLED: boolean;
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
TELEMETRY_ANONYMIZATION_ENABLED?: boolean; TELEMETRY_ANONYMIZATION_ENABLED: boolean;
@CastToPositiveNumber() @CastToPositiveNumber()
@IsNumber() @IsNumber()
@ -82,6 +85,7 @@ export class EnvironmentVariables {
PORT: number; PORT: number;
// Database // Database
@IsDefined()
@IsUrl({ @IsUrl({
protocols: ['postgres'], protocols: ['postgres'],
require_tld: false, require_tld: false,
@ -132,75 +136,79 @@ export class EnvironmentVariables {
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
AUTH_GOOGLE_ENABLED?: boolean; AUTH_GOOGLE_ENABLED: boolean;
@IsString() @IsString()
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true) @ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
AUTH_GOOGLE_CLIENT_ID?: string; AUTH_GOOGLE_CLIENT_ID: string;
@IsString() @IsString()
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true) @ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
AUTH_GOOGLE_CLIENT_SECRET?: string; AUTH_GOOGLE_CLIENT_SECRET: string;
@IsUrl({ require_tld: false }) @IsUrl({ require_tld: false })
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true) @ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
AUTH_GOOGLE_CALLBACK_URL?: string; AUTH_GOOGLE_CALLBACK_URL: string;
// Storage // Storage
@IsEnum(StorageDriverType) @IsEnum(StorageDriverType)
@IsOptional() @IsOptional()
STORAGE_TYPE?: StorageDriverType; STORAGE_TYPE: StorageDriverType;
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3) @ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
@IsAWSRegion() @IsAWSRegion()
STORAGE_S3_REGION?: AwsRegion; STORAGE_S3_REGION: AwsRegion;
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3) @ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
@IsString() @IsString()
STORAGE_S3_NAME?: string; STORAGE_S3_NAME: string;
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
@IsString()
STORAGE_S3_ENDPOINT: string;
@IsString() @IsString()
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.Local) @ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.Local)
STORAGE_LOCAL_PATH?: string; STORAGE_LOCAL_PATH: string;
// Support // Support
@IsEnum(SupportDriver) @IsEnum(SupportDriver)
@IsOptional() @IsOptional()
SUPPORT_DRIVER?: SupportDriver; SUPPORT_DRIVER: SupportDriver;
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front) @ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
@IsString() @IsString()
SUPPORT_FRONT_CHAT_ID?: string; SUPPORT_FRONT_CHAT_ID: string;
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front) @ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
@IsString() @IsString()
SUPPORT_FRONT_HMAC_KEY?: string; SUPPORT_FRONT_HMAC_KEY: string;
@IsEnum(LoggerDriverType) @IsEnum(LoggerDriverType)
@IsOptional() @IsOptional()
LOGGER_DRIVER?: LoggerDriverType; LOGGER_DRIVER: LoggerDriverType;
@IsEnum(ExceptionHandlerDriver) @IsEnum(ExceptionHandlerDriver)
@IsOptional() @IsOptional()
EXCEPTION_HANDLER_DRIVER?: ExceptionHandlerDriver; EXCEPTION_HANDLER_DRIVER: ExceptionHandlerDriver;
@CastToLogLevelArray() @CastToLogLevelArray()
@IsOptional() @IsOptional()
LOG_LEVELS?: LogLevel[]; LOG_LEVELS: LogLevel[];
@CastToStringArray() @CastToStringArray()
@IsOptional() @IsOptional()
DEMO_WORKSPACE_IDS?: string[]; DEMO_WORKSPACE_IDS: string[];
@ValidateIf( @ValidateIf(
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry, (env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
) )
@IsString() @IsString()
SENTRY_DSN?: string; SENTRY_DSN: string;
@IsDuration() @IsDuration()
@IsOptional() @IsOptional()
PASSWORD_RESET_TOKEN_EXPIRES_IN?: number; PASSWORD_RESET_TOKEN_EXPIRES_IN: string;
@CastToPositiveNumber() @CastToPositiveNumber()
@IsNumber() @IsNumber()
@ -220,13 +228,56 @@ export class EnvironmentVariables {
@CastToBoolean() @CastToBoolean()
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
IS_SIGN_UP_DISABLED?: boolean; IS_SIGN_UP_DISABLED: boolean;
@CastToPositiveNumber() @CastToPositiveNumber()
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
MUTATION_MAXIMUM_RECORD_AFFECTED: number; MUTATION_MAXIMUM_RECORD_AFFECTED: number;
REDIS_HOST: string;
REDIS_PORT: number;
API_TOKEN_EXPIRES_IN: string;
SHORT_TERM_TOKEN_EXPIRES_IN: string;
MESSAGING_PROVIDER_GMAIL_ENABLED: boolean;
MESSAGING_PROVIDER_GMAIL_CALLBACK_URL: string;
MESSAGE_QUEUE_TYPE: string;
EMAIL_FROM_ADDRESS: string;
EMAIL_SYSTEM_ADDRESS: string;
EMAIL_FROM_NAME: string;
EMAIL_DRIVER: EmailDriver;
EMAIL_SMTP_HOST: string;
EMAIL_SMTP_PORT: number;
EMAIL_SMTP_USER: string;
EMAIL_SMTP_PASSWORD: string;
OPENROUTER_API_KEY: string;
API_RATE_LIMITING_TTL: number;
API_RATE_LIMITING_LIMIT: number;
CACHE_STORAGE_TYPE: string;
CACHE_STORAGE_TTL: number;
CALENDAR_PROVIDER_GOOGLE_ENABLED: boolean;
AUTH_GOOGLE_APIS_CALLBACK_URL: string;
} }
export const validate = (config: Record<string, unknown>) => { export const validate = (config: Record<string, unknown>) => {
const validatedConfig = plainToClass(EnvironmentVariables, config); const validatedConfig = plainToClass(EnvironmentVariables, config);

View File

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

View File

@ -3,7 +3,7 @@ import { ConfigModule } from '@nestjs/config';
import { EnvironmentService } from './environment.service'; import { EnvironmentService } from './environment.service';
import { ConfigurableModuleClass } from './environment.module-definition'; import { ConfigurableModuleClass } from './environment.module-definition';
import { validate } from './environment.validation'; import { validate } from './environment-variables';
@Global() @Global()
@Module({ @Module({

View File

@ -1,92 +1,23 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* 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 { ConfigService } from '@nestjs/config';
import { Request } from 'express'; import { Request } from 'express';
import { EmailDriver } from 'src/integrations/email/interfaces/email.interface'; import { EnvironmentVariables } from 'src/integrations/environment/environment-variables';
import { EnvironmentDefault } from 'src/integrations/environment/environment.default';
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';
@Injectable() @Injectable()
export class EnvironmentService { export class EnvironmentService {
constructor(private configService: ConfigService) {} constructor(private configService: ConfigService) {}
isDebugMode(): boolean { get<T extends keyof EnvironmentVariables>(key: T): EnvironmentVariables[T] {
return this.configService.get<boolean>('DEBUG_MODE') ?? false;
}
isSignInPrefilled(): boolean {
return this.configService.get<boolean>('SIGN_IN_PREFILLED') ?? false;
}
isBillingEnabled() {
return this.configService.get<boolean>('IS_BILLING_ENABLED') ?? false;
}
getBillingUrl() {
return this.configService.get<string>('BILLING_PLAN_REQUIRED_LINK') ?? '';
}
getBillingStripeBasePlanProductId(): string {
return ( return (
this.configService.get<string>('BILLING_STRIPE_BASE_PLAN_PRODUCT_ID') ?? this.configService.get<EnvironmentVariables[T]>(key) ??
'' EnvironmentDefault[key]
); );
} }
getBillingStripeApiKey(): string {
return this.configService.get<string>('BILLING_STRIPE_API_KEY') ?? '';
}
getBillingStripeWebhookSecret(): string {
return (
this.configService.get<string>('BILLING_STRIPE_WEBHOOK_SECRET') ?? ''
);
}
getBillingFreeTrialDurationInDays(): number {
return (
this.configService.get<number>('BILLING_FREE_TRIAL_DURATION_IN_DAYS') ?? 7
);
}
isTelemetryEnabled(): boolean {
return this.configService.get<boolean>('TELEMETRY_ENABLED') ?? true;
}
isTelemetryAnonymizationEnabled(): boolean {
return (
this.configService.get<boolean>('TELEMETRY_ANONYMIZATION_ENABLED') ?? true
);
}
getPort(): number {
return this.configService.get<number>('PORT') ?? 3000;
}
getPGDatabaseUrl(): string {
return this.configService.get<string>('PG_DATABASE_URL')!;
}
getRedisHost(): string {
return this.configService.get<string>('REDIS_HOST') ?? '127.0.0.1';
}
getRedisPort(): number {
return +(this.configService.get<string>('REDIS_PORT') ?? 6379);
}
getFrontBaseUrl(): string {
return this.configService.get<string>('FRONT_BASE_URL')!;
}
getServerUrl(): string { getServerUrl(): string {
const url = this.configService.get<string>('SERVER_URL')!; const url = this.configService.get<string>('SERVER_URL')!;
@ -103,262 +34,15 @@ export class EnvironmentService {
); );
} }
getAccessTokenSecret(): string {
return this.configService.get<string>('ACCESS_TOKEN_SECRET')!;
}
getAccessTokenExpiresIn(): string {
return this.configService.get<string>('ACCESS_TOKEN_EXPIRES_IN') ?? '30m';
}
getRefreshTokenSecret(): string {
return this.configService.get<string>('REFRESH_TOKEN_SECRET')!;
}
getRefreshTokenExpiresIn(): string {
return this.configService.get<string>('REFRESH_TOKEN_EXPIRES_IN') ?? '90d';
}
getRefreshTokenCoolDown(): string {
return this.configService.get<string>('REFRESH_TOKEN_COOL_DOWN') ?? '1m';
}
getLoginTokenSecret(): string {
return this.configService.get<string>('LOGIN_TOKEN_SECRET')!;
}
getLoginTokenExpiresIn(): string {
return this.configService.get<string>('LOGIN_TOKEN_EXPIRES_IN') ?? '15m';
}
getTransientTokenExpiresIn(): string {
return (
this.configService.get<string>('SHORT_TERM_TOKEN_EXPIRES_IN') ?? '5m'
);
}
getApiTokenExpiresIn(): string {
return this.configService.get<string>('API_TOKEN_EXPIRES_IN') ?? '1000y';
}
getFrontAuthCallbackUrl(): string { getFrontAuthCallbackUrl(): string {
return ( return (
this.configService.get<string>('FRONT_AUTH_CALLBACK_URL') ?? this.configService.get<string>('FRONT_AUTH_CALLBACK_URL') ??
this.getFrontBaseUrl() + '/verify' this.get('FRONT_BASE_URL') + '/verify'
);
}
isMessagingProviderGmailEnabled(): boolean {
return (
this.configService.get<boolean>('MESSAGING_PROVIDER_GMAIL_ENABLED') ??
false
);
}
isCalendarProviderGoogleEnabled(): boolean {
return (
this.configService.get<boolean>('CALENDAR_PROVIDER_GOOGLE_ENABLED') ??
false
);
}
getMessagingProviderGmailCallbackUrl(): string | undefined {
return this.configService.get<string>(
'MESSAGING_PROVIDER_GMAIL_CALLBACK_URL',
);
}
getAuthGoogleAPIsCallbackUrl(): string | undefined {
return this.configService.get<string>('AUTH_GOOGLE_APIS_CALLBACK_URL');
}
isAuthGoogleEnabled(): boolean {
return this.configService.get<boolean>('AUTH_GOOGLE_ENABLED') ?? false;
}
getAuthGoogleClientId(): string | undefined {
return this.configService.get<string>('AUTH_GOOGLE_CLIENT_ID');
}
getAuthGoogleClientSecret(): string | undefined {
return this.configService.get<string>('AUTH_GOOGLE_CLIENT_SECRET');
}
getAuthGoogleCallbackUrl(): string | undefined {
return this.configService.get<string>('AUTH_GOOGLE_CALLBACK_URL');
}
getStorageDriverType(): StorageDriverType {
return (
this.configService.get<StorageDriverType>('STORAGE_TYPE') ??
StorageDriverType.Local
);
}
getMessageQueueDriverType(): MessageQueueDriverType {
return (
this.configService.get<MessageQueueDriverType>('MESSAGE_QUEUE_TYPE') ??
MessageQueueDriverType.Sync
);
}
getStorageS3Region(): AwsRegion | undefined {
return this.configService.get<AwsRegion>('STORAGE_S3_REGION');
}
getStorageS3Name(): string | undefined {
return this.configService.get<string>('STORAGE_S3_NAME');
}
getStorageS3Endpoint(): string | undefined {
return this.configService.get<string>('STORAGE_S3_ENDPOINT');
}
getStorageLocalPath(): string {
return (
this.configService.get<string>('STORAGE_LOCAL_PATH') ?? '.local-storage'
);
}
getEmailFromAddress(): string {
return (
this.configService.get<string>('EMAIL_FROM_ADDRESS') ??
'noreply@yourdomain.com'
);
}
getEmailSystemAddress(): string {
return (
this.configService.get<string>('EMAIL_SYSTEM_ADDRESS') ??
'system@yourdomain.com'
);
}
getEmailFromName(): string {
return (
this.configService.get<string>('EMAIL_FROM_NAME') ??
'John from YourDomain'
);
}
getEmailDriver(): EmailDriver {
return (
this.configService.get<EmailDriver>('EMAIL_DRIVER') ?? EmailDriver.Logger
);
}
getEmailHost(): string | undefined {
return this.configService.get<string>('EMAIL_SMTP_HOST');
}
getEmailPort(): number | undefined {
return this.configService.get<number>('EMAIL_SMTP_PORT');
}
getEmailUser(): string | undefined {
return this.configService.get<string>('EMAIL_SMTP_USER');
}
getEmailPassword(): string | undefined {
return this.configService.get<string>('EMAIL_SMTP_PASSWORD');
}
getSupportDriver(): string {
return (
this.configService.get<string>('SUPPORT_DRIVER') ?? SupportDriver.None
);
}
getSupportFrontChatId(): string | undefined {
return this.configService.get<string>('SUPPORT_FRONT_CHAT_ID');
}
getSupportFrontHMACKey(): string | undefined {
return this.configService.get<string>('SUPPORT_FRONT_HMAC_KEY');
}
getLoggerDriverType(): LoggerDriverType {
return (
this.configService.get<LoggerDriverType>('LOGGER_DRIVER') ??
LoggerDriverType.Console
); );
} }
// TODO: check because it isn't called
getLoggerIsBufferEnabled(): boolean | undefined { getLoggerIsBufferEnabled(): boolean | undefined {
return this.configService.get<boolean>('LOGGER_IS_BUFFER_ENABLED') ?? true; return this.configService.get<boolean>('LOGGER_IS_BUFFER_ENABLED') ?? true;
} }
getExceptionHandlerDriverType(): ExceptionHandlerDriver {
return (
this.configService.get<ExceptionHandlerDriver>(
'EXCEPTION_HANDLER_DRIVER',
) ?? ExceptionHandlerDriver.Console
);
}
getLogLevels(): LogLevel[] {
return (
this.configService.get<LogLevel[]>('LOG_LEVELS') ?? [
'log',
'error',
'warn',
]
);
}
getSentryDSN(): string | undefined {
return this.configService.get<string | undefined>('SENTRY_DSN');
}
getDemoWorkspaceIds(): string[] {
return this.configService.get<string[]>('DEMO_WORKSPACE_IDS') ?? [];
}
getOpenRouterApiKey(): string | undefined {
return this.configService.get<string | undefined>('OPENROUTER_API_KEY');
}
getPasswordResetTokenExpiresIn(): string {
return (
this.configService.get<string>('PASSWORD_RESET_TOKEN_EXPIRES_IN') ?? '5m'
);
}
getInactiveDaysBeforeEmail(): number | undefined {
return this.configService.get<number | undefined>(
'WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION',
);
}
getInactiveDaysBeforeDelete(): number | undefined {
return this.configService.get<number | undefined>(
'WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION',
);
}
isSignUpDisabled(): boolean {
return this.configService.get<boolean>('IS_SIGN_UP_DISABLED') ?? false;
}
getApiRateLimitingTtl(): number {
return this.configService.get<number>('API_RATE_LIMITING_TTL') ?? 100;
}
getApiRateLimitingLimit(): number {
return this.configService.get<number>('API_RATE_LIMITING_LIMIT') ?? 500;
}
getMutationMaximumRecordAffected(): number {
return (
this.configService.get<number>('MUTATION_MAXIMUM_RECORD_AFFECTED') ?? 100
);
}
getCacheStorageType(): string {
return this.configService.get<string>('CACHE_STORAGE_TYPE') ?? 'memory';
}
getCacheStorageTtl(): number {
return this.configService.get<number>('CACHE_STORAGE_TTL') ?? 3600 * 24 * 7;
}
} }

View File

@ -13,7 +13,7 @@ export const exceptionHandlerModuleFactory = async (
environmentService: EnvironmentService, environmentService: EnvironmentService,
adapterHost: HttpAdapterHost, adapterHost: HttpAdapterHost,
): Promise<typeof OPTIONS_TYPE> => { ): Promise<typeof OPTIONS_TYPE> => {
const driverType = environmentService.getExceptionHandlerDriverType(); const driverType = environmentService.get('EXCEPTION_HANDLER_DRIVER');
switch (driverType) { switch (driverType) {
case ExceptionHandlerDriver.Console: { case ExceptionHandlerDriver.Console: {
@ -25,9 +25,9 @@ export const exceptionHandlerModuleFactory = async (
return { return {
type: ExceptionHandlerDriver.Sentry, type: ExceptionHandlerDriver.Sentry,
options: { options: {
dsn: environmentService.getSentryDSN() ?? '', dsn: environmentService.get('SENTRY_DSN') ?? '',
serverInstance: adapterHost.httpAdapter?.getInstance(), serverInstance: adapterHost.httpAdapter?.getInstance(),
debug: environmentService.isDebugMode(), debug: environmentService.get('DEBUG_MODE'),
}, },
}; };
} }

View File

@ -14,11 +14,11 @@ import {
export const fileStorageModuleFactory = async ( export const fileStorageModuleFactory = async (
environmentService: EnvironmentService, environmentService: EnvironmentService,
): Promise<FileStorageModuleOptions> => { ): Promise<FileStorageModuleOptions> => {
const driverType = environmentService.getStorageDriverType(); const driverType = environmentService.get('STORAGE_TYPE');
switch (driverType) { switch (driverType) {
case StorageDriverType.Local: { case StorageDriverType.Local: {
const storagePath = environmentService.getStorageLocalPath(); const storagePath = environmentService.get('STORAGE_LOCAL_PATH');
return { return {
type: StorageDriverType.Local, type: StorageDriverType.Local,
@ -28,9 +28,9 @@ export const fileStorageModuleFactory = async (
}; };
} }
case StorageDriverType.S3: { case StorageDriverType.S3: {
const bucketName = environmentService.getStorageS3Name(); const bucketName = environmentService.get('STORAGE_S3_NAME');
const endpoint = environmentService.getStorageS3Endpoint(); const endpoint = environmentService.get('STORAGE_S3_ENDPOINT');
const region = environmentService.getStorageS3Region(); const region = environmentService.get('STORAGE_S3_REGION');
return { return {
type: StorageDriverType.S3, type: StorageDriverType.S3,

View File

@ -12,8 +12,8 @@ import {
export const loggerModuleFactory = async ( export const loggerModuleFactory = async (
environmentService: EnvironmentService, environmentService: EnvironmentService,
): Promise<LoggerModuleOptions> => { ): Promise<LoggerModuleOptions> => {
const driverType = environmentService.getLoggerDriverType(); const driverType = environmentService.get('LOGGER_DRIVER');
const logLevels = environmentService.getLogLevels(); const logLevels = environmentService.get('LOG_LEVELS');
switch (driverType) { switch (driverType) {
case LoggerDriverType.Console: { case LoggerDriverType.Console: {

View File

@ -12,7 +12,7 @@ import {
export const messageQueueModuleFactory = async ( export const messageQueueModuleFactory = async (
environmentService: EnvironmentService, environmentService: EnvironmentService,
): Promise<MessageQueueModuleOptions> => { ): Promise<MessageQueueModuleOptions> => {
const driverType = environmentService.getMessageQueueDriverType(); const driverType = environmentService.get('MESSAGE_QUEUE_TYPE');
switch (driverType) { switch (driverType) {
case MessageQueueDriverType.Sync: { case MessageQueueDriverType.Sync: {
@ -22,7 +22,7 @@ export const messageQueueModuleFactory = async (
}; };
} }
case MessageQueueDriverType.PgBoss: { case MessageQueueDriverType.PgBoss: {
const connectionString = environmentService.getPGDatabaseUrl(); const connectionString = environmentService.get('PG_DATABASE_URL');
return { return {
type: MessageQueueDriverType.PgBoss, type: MessageQueueDriverType.PgBoss,
@ -32,8 +32,8 @@ export const messageQueueModuleFactory = async (
}; };
} }
case MessageQueueDriverType.BullMQ: { case MessageQueueDriverType.BullMQ: {
const host = environmentService.getRedisHost(); const host = environmentService.get('REDIS_HOST');
const port = environmentService.getRedisPort(); const port = environmentService.get('REDIS_PORT');
return { return {
type: MessageQueueDriverType.BullMQ, type: MessageQueueDriverType.BullMQ,

View File

@ -53,7 +53,7 @@ const bootstrap = async () => {
}), }),
); );
await app.listen(app.get(EnvironmentService).getPort()); await app.listen(app.get(EnvironmentService).get('PORT'));
}; };
bootstrap(); bootstrap();

View File

@ -26,8 +26,8 @@ export const metadataModuleFactory = async (
resolvers: { JSON: GraphQLJSON }, resolvers: { JSON: GraphQLJSON },
plugins: [ plugins: [
useThrottler({ useThrottler({
ttl: environmentService.getApiRateLimitingTtl(), ttl: environmentService.get('API_RATE_LIMITING_TTL'),
limit: environmentService.getApiRateLimitingLimit(), limit: environmentService.get('API_RATE_LIMITING_LIMIT'),
identifyFn: (context) => { identifyFn: (context) => {
return context.user?.id ?? context.req.ip ?? 'anonymous'; return context.user?.id ?? context.req.ip ?? 'anonymous';
}, },
@ -39,7 +39,7 @@ export const metadataModuleFactory = async (
path: '/metadata', path: '/metadata',
}; };
if (environmentService.isDebugMode()) { if (environmentService.get('DEBUG_MODE')) {
config.renderGraphiQL = () => { config.renderGraphiQL = () => {
return renderApolloPlayground({ path: 'metadata' }); return renderApolloPlayground({ path: 'metadata' });
}; };

View File

@ -48,8 +48,8 @@ export class GoogleAPIsRefreshAccessTokenService {
const response = await axios.post( const response = await axios.post(
'https://oauth2.googleapis.com/token', 'https://oauth2.googleapis.com/token',
{ {
client_id: this.environmentService.getAuthGoogleClientId(), client_id: this.environmentService.get('AUTH_GOOGLE_CLIENT_ID'),
client_secret: this.environmentService.getAuthGoogleClientSecret(), client_secret: this.environmentService.get('AUTH_GOOGLE_CLIENT_SECRET'),
refresh_token: refreshToken, refresh_token: refreshToken,
grant_type: 'refresh_token', grant_type: 'refresh_token',
}, },

View File

@ -23,10 +23,12 @@ export class GoogleCalendarClientProvider {
} }
private async getOAuth2Client(refreshToken: string): Promise<OAuth2Client> { private async getOAuth2Client(refreshToken: string): Promise<OAuth2Client> {
const googleCalendarClientId = const googleCalendarClientId = this.environmentService.get(
this.environmentService.getAuthGoogleClientId(); 'AUTH_GOOGLE_CLIENT_ID',
const googleCalendarClientSecret = );
this.environmentService.getAuthGoogleClientSecret(); const googleCalendarClientSecret = this.environmentService.get(
'AUTH_GOOGLE_CLIENT_SECRET',
);
const oAuth2Client = new google.auth.OAuth2( const oAuth2Client = new google.auth.OAuth2(
googleCalendarClientId, googleCalendarClientId,

View File

@ -21,9 +21,9 @@ export class GmailClientProvider {
} }
private async getOAuth2Client(refreshToken: string): Promise<OAuth2Client> { private async getOAuth2Client(refreshToken: string): Promise<OAuth2Client> {
const gmailClientId = this.environmentService.getAuthGoogleClientId(); const gmailClientId = this.environmentService.get('AUTH_GOOGLE_CLIENT_ID');
const gmailClientSecret = const gmailClientSecret =
this.environmentService.getAuthGoogleClientSecret(); this.environmentService.get('AUTH_GOOGLE_CLIENT_SECRET');
const oAuth2Client = new google.auth.OAuth2( const oAuth2Client = new google.auth.OAuth2(
gmailClientId, gmailClientId,

View File

@ -45,9 +45,9 @@ export class CleanInactiveWorkspaceJob
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
) { ) {
this.inactiveDaysBeforeDelete = this.inactiveDaysBeforeDelete =
this.environmentService.getInactiveDaysBeforeDelete(); this.environmentService.get('WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION');
this.inactiveDaysBeforeEmail = this.inactiveDaysBeforeEmail =
this.environmentService.getInactiveDaysBeforeEmail(); this.environmentService.get('WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION');
} }
async getMostRecentUpdatedAt( async getMostRecentUpdatedAt(
@ -133,8 +133,8 @@ export class CleanInactiveWorkspaceJob
this.emailService.send({ this.emailService.send({
to: workspaceMember.email, to: workspaceMember.email,
bcc: this.environmentService.getEmailSystemAddress(), bcc: this.environmentService.get('EMAIL_SYSTEM_ADDRESS'),
from: `${this.environmentService.getEmailFromName()} <${this.environmentService.getEmailFromAddress()}>`, from: `${this.environmentService.get('EMAIL_FROM_NAME')} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`,
subject: 'Action Needed to Prevent Workspace Deletion', subject: 'Action Needed to Prevent Workspace Deletion',
html, html,
text, text,
@ -179,8 +179,8 @@ export class CleanInactiveWorkspaceJob
}); });
await this.emailService.send({ await this.emailService.send({
to: this.environmentService.getEmailSystemAddress(), to: this.environmentService.get('EMAIL_SYSTEM_ADDRESS'),
from: `${this.environmentService.getEmailFromName()} <${this.environmentService.getEmailFromAddress()}>`, from: `${this.environmentService.get('EMAIL_FROM_NAME')} <${this.environmentService.get('EMAIL_FROM_ADDRESS')}>`,
subject: 'Action Needed to Delete Workspaces', subject: 'Action Needed to Delete Workspaces',
html, html,
text, text,

View File

@ -307,7 +307,7 @@ export class WorkspaceQueryRunnerService {
): Promise<Record[] | undefined> { ): Promise<Record[] | undefined> {
const { workspaceId, objectMetadataItem } = options; const { workspaceId, objectMetadataItem } = options;
const maximumRecordAffected = const maximumRecordAffected =
this.environmentService.getMutationMaximumRecordAffected(); this.environmentService.get('MUTATION_MAXIMUM_RECORD_AFFECTED');
const query = await this.workspaceQueryBuilderFactory.updateMany(args, { const query = await this.workspaceQueryBuilderFactory.updateMany(args, {
...options, ...options,
atMost: maximumRecordAffected, atMost: maximumRecordAffected,
@ -339,7 +339,7 @@ export class WorkspaceQueryRunnerService {
): Promise<Record[] | undefined> { ): Promise<Record[] | undefined> {
const { workspaceId, objectMetadataItem } = options; const { workspaceId, objectMetadataItem } = options;
const maximumRecordAffected = const maximumRecordAffected =
this.environmentService.getMutationMaximumRecordAffected(); this.environmentService.get('MUTATION_MAXIMUM_RECORD_AFFECTED');
const query = await this.workspaceQueryBuilderFactory.deleteMany(args, { const query = await this.workspaceQueryBuilderFactory.deleteMany(args, {
...options, ...options,
atMost: maximumRecordAffected, atMost: maximumRecordAffected,