diff --git a/packages/twenty-docker/.env.example b/packages/twenty-docker/.env.example index 5a3ab70a71f9..c83e13c54322 100644 --- a/packages/twenty-docker/.env.example +++ b/packages/twenty-docker/.env.example @@ -7,6 +7,9 @@ PG_DATABASE_HOST=db:5432 REDIS_URL=redis://redis:6379 SERVER_URL=http://localhost:3000 +FRONT_DOMAIN=localhost +FRONT_PORT=3000 +FRONT_PROTOCOL=http # Use openssl rand -base64 32 for each secret # APP_SECRET=replace_me_with_a_random_string diff --git a/packages/twenty-docker/docker-compose.yml b/packages/twenty-docker/docker-compose.yml index 2dcb01eebb24..dfc389608149 100644 --- a/packages/twenty-docker/docker-compose.yml +++ b/packages/twenty-docker/docker-compose.yml @@ -24,7 +24,6 @@ services: PORT: 3000 PG_DATABASE_URL: postgres://${PGUSER_SUPERUSER:-postgres}:${PGPASSWORD_SUPERUSER:-postgres}@${PG_DATABASE_HOST:-db:5432}/default SERVER_URL: ${SERVER_URL} - FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL} REDIS_URL: ${REDIS_URL:-redis://redis:6379} ENABLE_DB_MIGRATIONS: "true" @@ -54,9 +53,7 @@ services: environment: PG_DATABASE_URL: postgres://${PGUSER_SUPERUSER:-postgres}:${PGPASSWORD_SUPERUSER:-postgres}@${PG_DATABASE_HOST:-db:5432}/default SERVER_URL: ${SERVER_URL} - FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL} REDIS_URL: ${REDIS_URL:-redis://redis:6379} - ENABLE_DB_MIGRATIONS: "false" # it already runs on the server STORAGE_TYPE: ${STORAGE_TYPE} diff --git a/packages/twenty-docker/k8s/manifests/deployment-server.yaml b/packages/twenty-docker/k8s/manifests/deployment-server.yaml index fe5b37983646..86f601ef1dd3 100644 --- a/packages/twenty-docker/k8s/manifests/deployment-server.yaml +++ b/packages/twenty-docker/k8s/manifests/deployment-server.yaml @@ -37,8 +37,6 @@ spec: value: 3000 - name: SERVER_URL value: "https://crm.example.com:443" - - name: FRONT_BASE_URL - value: "https://crm.example.com:443" - name: "PG_DATABASE_URL" value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default" - name: "REDIS_URL" diff --git a/packages/twenty-docker/k8s/manifests/deployment-worker.yaml b/packages/twenty-docker/k8s/manifests/deployment-worker.yaml index cdb200c2b15c..e379bdafa6c8 100644 --- a/packages/twenty-docker/k8s/manifests/deployment-worker.yaml +++ b/packages/twenty-docker/k8s/manifests/deployment-worker.yaml @@ -28,8 +28,6 @@ spec: env: - name: SERVER_URL value: "https://crm.example.com:443" - - name: FRONT_BASE_URL - value: "https://crm.example.com:443" - name: PG_DATABASE_URL value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default" - name: ENABLE_DB_MIGRATIONS diff --git a/packages/twenty-docker/k8s/terraform/deployment-server.tf b/packages/twenty-docker/k8s/terraform/deployment-server.tf index 5276d574319e..b8987fc01edf 100644 --- a/packages/twenty-docker/k8s/terraform/deployment-server.tf +++ b/packages/twenty-docker/k8s/terraform/deployment-server.tf @@ -51,11 +51,6 @@ resource "kubernetes_deployment" "twentycrm_server" { value = var.twentycrm_app_hostname } - env { - name = "FRONT_BASE_URL" - value = var.twentycrm_app_hostname - } - env { name = "PG_DATABASE_URL" value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${kubernetes_service.twentycrm_db.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default" diff --git a/packages/twenty-docker/k8s/terraform/deployment-worker.tf b/packages/twenty-docker/k8s/terraform/deployment-worker.tf index aa68fd3af2da..ce55fff80813 100644 --- a/packages/twenty-docker/k8s/terraform/deployment-worker.tf +++ b/packages/twenty-docker/k8s/terraform/deployment-worker.tf @@ -43,11 +43,6 @@ resource "kubernetes_deployment" "twentycrm_worker" { value = var.twentycrm_app_hostname } - env { - name = "FRONT_BASE_URL" - value = var.twentycrm_app_hostname - } - env { name = "PG_DATABASE_URL" value = "postgres://twenty:${var.twentycrm_pgdb_admin_password}@${kubernetes_service.twentycrm_db.metadata.0.name}.${kubernetes_namespace.twentycrm.metadata.0.name}.svc.cluster.local/default" diff --git a/packages/twenty-server/.env.example b/packages/twenty-server/.env.example index d6fd699934b3..1b0fed342ba1 100644 --- a/packages/twenty-server/.env.example +++ b/packages/twenty-server/.env.example @@ -2,13 +2,15 @@ PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/default REDIS_URL=redis://localhost:6379 -FRONT_BASE_URL=http://localhost:3001 - APP_SECRET=replace_me_with_a_random_string SIGN_IN_PREFILLED=true ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access +FRONT_PROTOCOL=http +FRONT_DOMAIN=localhost +FRONT_PORT=3001 + # ———————— Optional ———————— # PORT=3000 # DEBUG_MODE=true diff --git a/packages/twenty-server/.env.test b/packages/twenty-server/.env.test index b0ba4b8c3d70..f6585868e3d7 100644 --- a/packages/twenty-server/.env.test +++ b/packages/twenty-server/.env.test @@ -3,7 +3,6 @@ REDIS_URL=redis://localhost:6379 DEBUG_MODE=true DEBUG_PORT=9000 -FRONT_BASE_URL=http://localhost:3001 APP_SECRET=replace_me_with_a_random_string SIGN_IN_PREFILLED=true EXCEPTION_HANDLER_DRIVER=console diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts index 1e140ce8d82e..401ce04b0a4e 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts @@ -6,7 +6,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service'; -import { AuthExceptionHandlerService } from 'src/engine/core-modules/auth/auth-exception-handler.service'; import { GoogleAPIsAuthController } from 'src/engine/core-modules/auth/controllers/google-apis-auth.controller'; import { GoogleAuthController } from 'src/engine/core-modules/auth/controllers/google-auth.controller'; import { MicrosoftAPIsAuthController } from 'src/engine/core-modules/auth/controllers/microsoft-apis-auth.controller'; @@ -103,7 +102,6 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy'; ResetPasswordService, SwitchWorkspaceService, TransientTokenService, - AuthExceptionHandlerService, ApiKeyService, OAuthService, ], diff --git a/packages/twenty-server/src/engine/core-modules/auth/filters/auth-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/auth/filters/auth-rest-api-exception.filter.ts index 2b5f242e96d0..1c256f87e962 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/filters/auth-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/filters/auth-rest-api-exception.filter.ts @@ -2,16 +2,16 @@ import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; import { Response } from 'express'; -import { AuthExceptionHandlerService } from 'src/engine/core-modules/auth/auth-exception-handler.service'; import { AuthException, AuthExceptionCode, } from 'src/engine/core-modules/auth/auth.exception'; +import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; @Catch(AuthException) export class AuthRestApiExceptionFilter implements ExceptionFilter { constructor( - private readonly authExceptionHandlerService: AuthExceptionHandlerService, + private readonly httpExceptionHandlerService: HttpExceptionHandlerService, ) {} catch(exception: AuthException, host: ArgumentsHost) { @@ -21,7 +21,7 @@ export class AuthRestApiExceptionFilter implements ExceptionFilter { switch (exception.code) { case AuthExceptionCode.USER_NOT_FOUND: case AuthExceptionCode.CLIENT_NOT_FOUND: - return this.authExceptionHandlerService.handleError( + return this.httpExceptionHandlerService.handleError( exception, response, 404, @@ -29,13 +29,13 @@ export class AuthRestApiExceptionFilter implements ExceptionFilter { case AuthExceptionCode.INVALID_INPUT: case AuthExceptionCode.INVALID_DATA: case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE: - return this.authExceptionHandlerService.handleError( + return this.httpExceptionHandlerService.handleError( exception, response, 400, ); case AuthExceptionCode.FORBIDDEN_EXCEPTION: - return this.authExceptionHandlerService.handleError( + return this.httpExceptionHandlerService.handleError( exception, response, 401, @@ -43,14 +43,14 @@ export class AuthRestApiExceptionFilter implements ExceptionFilter { case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED: case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED: case AuthExceptionCode.SIGNUP_DISABLED: - return this.authExceptionHandlerService.handleError( + return this.httpExceptionHandlerService.handleError( exception, response, 403, ); case AuthExceptionCode.INTERNAL_SERVER_ERROR: default: - return this.authExceptionHandlerService.handleError( + return this.httpExceptionHandlerService.handleError( exception, response, 500, diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts index ec0788ac622a..f96ae829be33 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts @@ -26,6 +26,7 @@ import { } from 'src/engine/core-modules/auth/auth.util'; import { AuthorizeApp } from 'src/engine/core-modules/auth/dto/authorize-app.entity'; import { AuthorizeAppInput } from 'src/engine/core-modules/auth/dto/authorize-app.input'; +import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; import { ChallengeInput } from 'src/engine/core-modules/auth/dto/challenge.input'; import { UpdatePassword } from 'src/engine/core-modules/auth/dto/update-password.entity'; import { @@ -37,17 +38,16 @@ import { WorkspaceInviteHashValid } from 'src/engine/core-modules/auth/dto/works import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service'; import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service'; +import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { EmailService } from 'src/engine/core-modules/email/email.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; -import { User } from 'src/engine/core-modules/user/user.entity'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; -import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; -import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; import { UserService } from 'src/engine/core-modules/user/services/user.service'; +import { User } from 'src/engine/core-modules/user/user.entity'; import { userValidator } from 'src/engine/core-modules/user/user.validate'; -import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; +import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Injectable() // eslint-disable-next-line @nx/workspace-inject-workspace-repository diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/reset-password.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/reset-password.service.ts index 0f7a14da7e74..90e9b13db54d 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/reset-password.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/reset-password.service.ts @@ -21,10 +21,10 @@ import { EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/email-p import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity'; import { PasswordResetToken } from 'src/engine/core-modules/auth/dto/token.entity'; import { ValidatePasswordResetToken } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.entity'; +import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { EmailService } from 'src/engine/core-modules/email/email.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { User } from 'src/engine/core-modules/user/user.entity'; -import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; @Injectable() export class ResetPasswordService { diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts index 61179279c105..a7dc7fe5f4d6 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.controller.ts @@ -6,6 +6,7 @@ import { RawBodyRequest, Req, Res, + UseFilters, } from '@nestjs/common'; import { Response } from 'express'; @@ -15,11 +16,13 @@ import { BillingExceptionCode, } from 'src/engine/core-modules/billing/billing.exception'; import { WebhookEvent } from 'src/engine/core-modules/billing/enums/billing-webhook-events.enum'; +import { BillingRestApiExceptionFilter } from 'src/engine/core-modules/billing/filters/billing-api-exception.filter'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; import { BillingWebhookEntitlementService } from 'src/engine/core-modules/billing/services/billing-webhook-entitlement.service'; import { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing/services/billing-webhook-subscription.service'; import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service'; @Controller('billing') +@UseFilters(BillingRestApiExceptionFilter) export class BillingController { protected readonly logger = new Logger(BillingController.name); diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts index 9b42e8878878..6fc9e399b779 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.module.ts @@ -10,6 +10,7 @@ import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-p import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity'; import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; +import { BillingRestApiExceptionFilter } from 'src/engine/core-modules/billing/filters/billing-api-exception.filter'; import { BillingWorkspaceMemberListener } from 'src/engine/core-modules/billing/listeners/billing-workspace-member.listener'; import { BillingPortalWorkspaceService } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; @@ -53,6 +54,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; BillingResolver, BillingWorkspaceMemberListener, BillingService, + BillingRestApiExceptionFilter, ], exports: [ BillingSubscriptionService, diff --git a/packages/twenty-server/src/engine/core-modules/billing/filters/billing-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/billing/filters/billing-api-exception.filter.ts new file mode 100644 index 000000000000..ace8707ee9ca --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/billing/filters/billing-api-exception.filter.ts @@ -0,0 +1,37 @@ +import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; + +import { Response } from 'express'; +import Stripe from 'stripe'; + +import { + BillingException, + BillingExceptionCode, +} from 'src/engine/core-modules/billing/billing.exception'; +import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; + +@Catch(BillingException, Stripe.errors.StripeError) +export class BillingRestApiExceptionFilter implements ExceptionFilter { + constructor( + private readonly httpExceptionHandlerService: HttpExceptionHandlerService, + ) {} + + catch(exception: BillingException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + switch (exception.code) { + case BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND: + return this.httpExceptionHandlerService.handleError( + exception, + response, + 404, + ); + default: + return this.httpExceptionHandlerService.handleError( + exception, + response, + 500, + ); + } + } +} diff --git a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts index 344b6d18992b..a5fccf02b6c1 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/services/billing-portal.workspace-service.ts @@ -31,10 +31,13 @@ export class BillingPortalWorkspaceService { priceId: string, successUrlPath?: string, ): Promise { - const frontBaseUrl = this.domainManagerService.getBaseUrl().toString(); - const successUrl = successUrlPath - ? frontBaseUrl + successUrlPath - : frontBaseUrl; + const frontBaseUrl = this.domainManagerService.getBaseUrl(); + const cancelUrl = frontBaseUrl.toString(); + + if (successUrlPath) { + frontBaseUrl.pathname = successUrlPath; + } + const successUrl = frontBaseUrl.toString(); const quantity = await this.userWorkspaceRepository.countBy({ workspaceId: workspace.id, @@ -51,7 +54,7 @@ export class BillingPortalWorkspaceService { priceId, quantity, successUrl, - frontBaseUrl, + cancelUrl, stripeCustomerId, ); @@ -81,10 +84,12 @@ export class BillingPortalWorkspaceService { throw new Error('Error: missing stripeCustomerId'); } - const frontBaseUrl = this.domainManagerService.getBaseUrl().toString(); - const returnUrl = returnUrlPath - ? frontBaseUrl + returnUrlPath - : frontBaseUrl; + const frontBaseUrl = this.domainManagerService.getBaseUrl(); + + if (returnUrlPath) { + frontBaseUrl.pathname = returnUrlPath; + } + const returnUrl = frontBaseUrl.toString(); const session = await this.stripeService.createBillingPortalSession( stripeCustomerId, diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.module.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.module.ts index 04c78007f207..70f57128f67f 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.module.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; +import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module'; + import { ClientConfigResolver } from './client-config.resolver'; @Module({ + imports: [DomainManagerModule], providers: [ClientConfigResolver], }) export class ClientConfigModule {} diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.spec.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.spec.ts index d1ecf6935710..a1f9169c2368 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.spec.ts @@ -1,5 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { ClientConfigResolver } from './client-config.resolver'; @@ -15,6 +16,10 @@ describe('ClientConfigResolver', () => { provide: EnvironmentService, useValue: {}, }, + { + provide: DomainManagerService, + useValue: {}, + }, ], }).compile(); diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts index 02d7412d4d8f..ef8fcfe73060 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.resolver.ts @@ -1,12 +1,16 @@ import { Query, Resolver } from '@nestjs/graphql'; +import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { ClientConfig } from './client-config.entity'; @Resolver() export class ClientConfigResolver { - constructor(private environmentService: EnvironmentService) {} + constructor( + private environmentService: EnvironmentService, + private domainManagerService: DomainManagerService, + ) {} @Query(() => ClientConfig) async clientConfig(): Promise { @@ -24,7 +28,7 @@ export class ClientConfigResolver { 'IS_MULTIWORKSPACE_ENABLED', ), defaultSubdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'), - frontDomain: this.environmentService.get('FRONT_DOMAIN'), + frontDomain: this.domainManagerService.getFrontUrl().hostname, debugMode: this.environmentService.get('DEBUG_MODE'), support: { supportDriver: this.environmentService.get('SUPPORT_DRIVER'), diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts index 093b96c405a1..eae5ed641b5a 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/domain-manager.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; - -import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Module({ - imports: [NestjsQueryTypeOrmModule.forFeature([Workspace], 'core')], + imports: [TypeOrmModule.forFeature([Workspace], 'core')], providers: [DomainManagerService], exports: [DomainManagerService], }) diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/service/domain-manager.service.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/service/domain-manager.service.ts index 626fb75f2fb0..5b711fbef743 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/service/domain-manager.service.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/service/domain-manager.service.ts @@ -21,10 +21,28 @@ export class DomainManagerService { private readonly environmentService: EnvironmentService, ) {} - getBaseUrl() { - const baseUrl = new URL( - `${this.environmentService.get('FRONT_PROTOCOL')}://${this.environmentService.get('FRONT_DOMAIN')}`, - ); + getFrontUrl() { + let baseUrl: URL; + + if (!this.environmentService.get('FRONT_DOMAIN')) { + baseUrl = new URL(this.environmentService.get('SERVER_URL')); + } else { + baseUrl = new URL( + `${this.environmentService.get('FRONT_PROTOCOL')}://${this.environmentService.get('FRONT_DOMAIN')}`, + ); + + const port = this.environmentService.get('FRONT_PORT'); + + if (port) { + baseUrl.port = port.toString(); + } + } + + return baseUrl; + } + + getBaseUrl(): URL { + const baseUrl = this.getFrontUrl(); if ( this.environmentService.get('IS_MULTIWORKSPACE_ENABLED') && @@ -33,10 +51,6 @@ export class DomainManagerService { baseUrl.hostname = `${this.environmentService.get('DEFAULT_SUBDOMAIN')}.${baseUrl.hostname}`; } - if (this.environmentService.get('FRONT_PORT')) { - baseUrl.port = this.environmentService.get('FRONT_PORT').toString(); - } - return baseUrl; } @@ -87,10 +101,9 @@ export class DomainManagerService { getWorkspaceSubdomainByOrigin = (origin: string) => { const { hostname: originHostname } = new URL(origin); - const subdomain = originHostname.replace( - `.${this.environmentService.get('FRONT_DOMAIN')}`, - '', - ); + const frontDomain = this.getFrontUrl().hostname; + + const subdomain = originHostname.replace(`.${frontDomain}`, ''); if (this.isDefaultSubdomain(subdomain)) { return; diff --git a/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts index 61db91a457a5..687c94c006f1 100644 --- a/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts @@ -129,7 +129,7 @@ export class EnvironmentVariables { // Frontend URL @IsString() @IsOptional() - FRONT_DOMAIN = 'localhost'; + FRONT_DOMAIN?: string; @IsString() @ValidateIf((env) => env.IS_MULTIWORKSPACE_ENABLED) @@ -137,12 +137,12 @@ export class EnvironmentVariables { @IsString() @IsOptional() - FRONT_PROTOCOL: 'http' | 'https' = 'http'; + FRONT_PROTOCOL?: 'http' | 'https' = 'http'; @CastToPositiveNumber() @IsNumber() @IsOptional() - FRONT_PORT = 3001; + FRONT_PORT?: number; @IsUrl({ require_tld: false, require_protocol: true }) @IsOptional() diff --git a/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts b/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts index 6df4e8aa761b..4028ed4eb91c 100644 --- a/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts +++ b/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts @@ -9,12 +9,13 @@ import { OPTIONS_TYPE, } from 'src/engine/core-modules/exception-handler/exception-handler.module-definition'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces'; @Global() @Module({ - providers: [ExceptionHandlerService], - exports: [ExceptionHandlerService], + providers: [ExceptionHandlerService, HttpExceptionHandlerService], + exports: [ExceptionHandlerService, HttpExceptionHandlerService], }) export class ExceptionHandlerModule extends ConfigurableModuleClass { static forRoot(options: typeof OPTIONS_TYPE): DynamicModule { diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth-exception-handler.service.ts b/packages/twenty-server/src/engine/core-modules/exception-handler/http-exception-handler.service.ts similarity index 90% rename from packages/twenty-server/src/engine/core-modules/auth/auth-exception-handler.service.ts rename to packages/twenty-server/src/engine/core-modules/exception-handler/http-exception-handler.service.ts index b5b6272c1c90..45e32f44db66 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth-exception-handler.service.ts +++ b/packages/twenty-server/src/engine/core-modules/exception-handler/http-exception-handler.service.ts @@ -6,11 +6,11 @@ import { Response } from 'express'; import { ExceptionHandlerUser } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-user.interface'; import { ExceptionHandlerWorkspace } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-workspace.interface'; -import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { CustomException } from 'src/utils/custom-exception'; export const handleException = ( - exception: AuthException, + exception: CustomException, exceptionHandlerService: ExceptionHandlerService, user?: ExceptionHandlerUser, workspace?: ExceptionHandlerWorkspace, @@ -24,7 +24,7 @@ interface RequestAndParams { } @Injectable({ scope: Scope.REQUEST }) -export class AuthExceptionHandlerService { +export class HttpExceptionHandlerService { constructor( private readonly exceptionHandlerService: ExceptionHandlerService, @Inject(REQUEST) @@ -32,7 +32,7 @@ export class AuthExceptionHandlerService { ) {} handleError = ( - exception: AuthException, + exception: CustomException, response: Response>, errorCode?: number, user?: ExceptionHandlerUser, diff --git a/packages/twenty-server/src/main.ts b/packages/twenty-server/src/main.ts index c6b4761c4263..28cce925ded8 100644 --- a/packages/twenty-server/src/main.ts +++ b/packages/twenty-server/src/main.ts @@ -4,15 +4,15 @@ import { NestExpressApplication } from '@nestjs/platform-express'; import fs from 'fs'; -import session from 'express-session'; import bytes from 'bytes'; import { useContainer } from 'class-validator'; +import session from 'express-session'; import { graphqlUploadExpress } from 'graphql-upload'; +import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; -import { ApplyCorsToExceptions } from 'src/utils/apply-cors-to-exceptions'; import { getSessionStorageOptions } from 'src/engine/core-modules/session-storage/session-storage.module-factory'; -import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; +import { UnhandledExceptionFilter } from 'src/utils/apply-cors-to-exceptions'; import { AppModule } from './app.module'; import './instrument'; @@ -48,7 +48,7 @@ const bootstrap = async () => { // Use our logger app.useLogger(logger); - app.useGlobalFilters(new ApplyCorsToExceptions()); + app.useGlobalFilters(new UnhandledExceptionFilter()); // Apply validation pipes globally app.useGlobalPipes( diff --git a/packages/twenty-server/src/utils/apply-cors-to-exceptions.ts b/packages/twenty-server/src/utils/apply-cors-to-exceptions.ts index eba73f8e0571..9d6fe1f0919a 100644 --- a/packages/twenty-server/src/utils/apply-cors-to-exceptions.ts +++ b/packages/twenty-server/src/utils/apply-cors-to-exceptions.ts @@ -11,7 +11,7 @@ import { Response } from 'express'; // the CORS headers are missing in the response. // This class add CORS headers to exception response to avoid misleading CORS error @Catch() -export class ApplyCorsToExceptions implements ExceptionFilter { +export class UnhandledExceptionFilter implements ExceptionFilter { catch(exception: any, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); @@ -20,6 +20,7 @@ export class ApplyCorsToExceptions implements ExceptionFilter { return; } + // TODO: Check if needed, remove otherwise. response.header('Access-Control-Allow-Origin', '*'); response.header( 'Access-Control-Allow-Methods', diff --git a/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx b/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx index fc3d785c4eb8..662cf559b1fb 100644 --- a/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx +++ b/packages/twenty-website/src/content/developers/self-hosting/upgrade-guide.mdx @@ -32,7 +32,14 @@ yarn command:prod upgrade-0.34 The `yarn database:migrate:prod` command will apply the migrations to the database structure (core and metadata schemas) The `yarn command:prod upgrade-0.34` takes care of the data migration of all workspaces. +**Environment Variables** + +- Removed: `FRONT_BASE_URL` +- Added: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` +We have updated the way we handle the frontend URL. +You can now set the frontend URL using the `FRONT_DOMAIN`, `FRONT_PROTOCOL` and `FRONT_PORT` variables. +If FRONT_DOMAIN is not set, the frontend URL will fallback to `SERVER_URL`. ### v0.32.0 to v0.33.0