Fast follows on 0.34 (#9034)

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Charles Bochet 2024-12-12 16:46:48 +01:00 committed by GitHub
parent 05cd0d1803
commit 77c2961912
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 141 additions and 76 deletions

View File

@ -7,6 +7,9 @@ PG_DATABASE_HOST=db:5432
REDIS_URL=redis://redis:6379 REDIS_URL=redis://redis:6379
SERVER_URL=http://localhost:3000 SERVER_URL=http://localhost:3000
FRONT_DOMAIN=localhost
FRONT_PORT=3000
FRONT_PROTOCOL=http
# Use openssl rand -base64 32 for each secret # Use openssl rand -base64 32 for each secret
# APP_SECRET=replace_me_with_a_random_string # APP_SECRET=replace_me_with_a_random_string

View File

@ -24,7 +24,6 @@ services:
PORT: 3000 PORT: 3000
PG_DATABASE_URL: postgres://${PGUSER_SUPERUSER:-postgres}:${PGPASSWORD_SUPERUSER:-postgres}@${PG_DATABASE_HOST:-db:5432}/default PG_DATABASE_URL: postgres://${PGUSER_SUPERUSER:-postgres}:${PGPASSWORD_SUPERUSER:-postgres}@${PG_DATABASE_HOST:-db:5432}/default
SERVER_URL: ${SERVER_URL} SERVER_URL: ${SERVER_URL}
FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379} REDIS_URL: ${REDIS_URL:-redis://redis:6379}
ENABLE_DB_MIGRATIONS: "true" ENABLE_DB_MIGRATIONS: "true"
@ -54,9 +53,7 @@ services:
environment: environment:
PG_DATABASE_URL: postgres://${PGUSER_SUPERUSER:-postgres}:${PGPASSWORD_SUPERUSER:-postgres}@${PG_DATABASE_HOST:-db:5432}/default PG_DATABASE_URL: postgres://${PGUSER_SUPERUSER:-postgres}:${PGPASSWORD_SUPERUSER:-postgres}@${PG_DATABASE_HOST:-db:5432}/default
SERVER_URL: ${SERVER_URL} SERVER_URL: ${SERVER_URL}
FRONT_BASE_URL: ${FRONT_BASE_URL:-$SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379} REDIS_URL: ${REDIS_URL:-redis://redis:6379}
ENABLE_DB_MIGRATIONS: "false" # it already runs on the server ENABLE_DB_MIGRATIONS: "false" # it already runs on the server
STORAGE_TYPE: ${STORAGE_TYPE} STORAGE_TYPE: ${STORAGE_TYPE}

View File

@ -37,8 +37,6 @@ spec:
value: 3000 value: 3000
- name: SERVER_URL - name: SERVER_URL
value: "https://crm.example.com:443" value: "https://crm.example.com:443"
- name: FRONT_BASE_URL
value: "https://crm.example.com:443"
- name: "PG_DATABASE_URL" - name: "PG_DATABASE_URL"
value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default" value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default"
- name: "REDIS_URL" - name: "REDIS_URL"

View File

@ -28,8 +28,6 @@ spec:
env: env:
- name: SERVER_URL - name: SERVER_URL
value: "https://crm.example.com:443" value: "https://crm.example.com:443"
- name: FRONT_BASE_URL
value: "https://crm.example.com:443"
- name: PG_DATABASE_URL - name: PG_DATABASE_URL
value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default" value: "postgres://postgres:postgres@twentycrm-db.twentycrm.svc.cluster.local/default"
- name: ENABLE_DB_MIGRATIONS - name: ENABLE_DB_MIGRATIONS

View File

@ -51,11 +51,6 @@ resource "kubernetes_deployment" "twentycrm_server" {
value = var.twentycrm_app_hostname value = var.twentycrm_app_hostname
} }
env {
name = "FRONT_BASE_URL"
value = var.twentycrm_app_hostname
}
env { env {
name = "PG_DATABASE_URL" 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" 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"

View File

@ -43,11 +43,6 @@ resource "kubernetes_deployment" "twentycrm_worker" {
value = var.twentycrm_app_hostname value = var.twentycrm_app_hostname
} }
env {
name = "FRONT_BASE_URL"
value = var.twentycrm_app_hostname
}
env { env {
name = "PG_DATABASE_URL" 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" 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"

View File

@ -2,13 +2,15 @@
PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/default PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/default
REDIS_URL=redis://localhost:6379 REDIS_URL=redis://localhost:6379
FRONT_BASE_URL=http://localhost:3001
APP_SECRET=replace_me_with_a_random_string APP_SECRET=replace_me_with_a_random_string
SIGN_IN_PREFILLED=true SIGN_IN_PREFILLED=true
ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access
FRONT_PROTOCOL=http
FRONT_DOMAIN=localhost
FRONT_PORT=3001
# ———————— Optional ———————— # ———————— Optional ————————
# PORT=3000 # PORT=3000
# DEBUG_MODE=true # DEBUG_MODE=true

View File

@ -3,7 +3,6 @@ REDIS_URL=redis://localhost:6379
DEBUG_MODE=true DEBUG_MODE=true
DEBUG_PORT=9000 DEBUG_PORT=9000
FRONT_BASE_URL=http://localhost:3001
APP_SECRET=replace_me_with_a_random_string APP_SECRET=replace_me_with_a_random_string
SIGN_IN_PREFILLED=true SIGN_IN_PREFILLED=true
EXCEPTION_HANDLER_DRIVER=console EXCEPTION_HANDLER_DRIVER=console

View File

@ -6,7 +6,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; 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 { 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 { 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 { GoogleAuthController } from 'src/engine/core-modules/auth/controllers/google-auth.controller';
import { MicrosoftAPIsAuthController } from 'src/engine/core-modules/auth/controllers/microsoft-apis-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, ResetPasswordService,
SwitchWorkspaceService, SwitchWorkspaceService,
TransientTokenService, TransientTokenService,
AuthExceptionHandlerService,
ApiKeyService, ApiKeyService,
OAuthService, OAuthService,
], ],

View File

@ -2,16 +2,16 @@ import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { Response } from 'express'; import { Response } from 'express';
import { AuthExceptionHandlerService } from 'src/engine/core-modules/auth/auth-exception-handler.service';
import { import {
AuthException, AuthException,
AuthExceptionCode, AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception'; } from 'src/engine/core-modules/auth/auth.exception';
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
@Catch(AuthException) @Catch(AuthException)
export class AuthRestApiExceptionFilter implements ExceptionFilter { export class AuthRestApiExceptionFilter implements ExceptionFilter {
constructor( constructor(
private readonly authExceptionHandlerService: AuthExceptionHandlerService, private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
) {} ) {}
catch(exception: AuthException, host: ArgumentsHost) { catch(exception: AuthException, host: ArgumentsHost) {
@ -21,7 +21,7 @@ export class AuthRestApiExceptionFilter implements ExceptionFilter {
switch (exception.code) { switch (exception.code) {
case AuthExceptionCode.USER_NOT_FOUND: case AuthExceptionCode.USER_NOT_FOUND:
case AuthExceptionCode.CLIENT_NOT_FOUND: case AuthExceptionCode.CLIENT_NOT_FOUND:
return this.authExceptionHandlerService.handleError( return this.httpExceptionHandlerService.handleError(
exception, exception,
response, response,
404, 404,
@ -29,13 +29,13 @@ export class AuthRestApiExceptionFilter implements ExceptionFilter {
case AuthExceptionCode.INVALID_INPUT: case AuthExceptionCode.INVALID_INPUT:
case AuthExceptionCode.INVALID_DATA: case AuthExceptionCode.INVALID_DATA:
case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE: case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE:
return this.authExceptionHandlerService.handleError( return this.httpExceptionHandlerService.handleError(
exception, exception,
response, response,
400, 400,
); );
case AuthExceptionCode.FORBIDDEN_EXCEPTION: case AuthExceptionCode.FORBIDDEN_EXCEPTION:
return this.authExceptionHandlerService.handleError( return this.httpExceptionHandlerService.handleError(
exception, exception,
response, response,
401, 401,
@ -43,14 +43,14 @@ export class AuthRestApiExceptionFilter implements ExceptionFilter {
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED: case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED: case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
case AuthExceptionCode.SIGNUP_DISABLED: case AuthExceptionCode.SIGNUP_DISABLED:
return this.authExceptionHandlerService.handleError( return this.httpExceptionHandlerService.handleError(
exception, exception,
response, response,
403, 403,
); );
case AuthExceptionCode.INTERNAL_SERVER_ERROR: case AuthExceptionCode.INTERNAL_SERVER_ERROR:
default: default:
return this.authExceptionHandlerService.handleError( return this.httpExceptionHandlerService.handleError(
exception, exception,
response, response,
500, 500,

View File

@ -26,6 +26,7 @@ import {
} from 'src/engine/core-modules/auth/auth.util'; } from 'src/engine/core-modules/auth/auth.util';
import { AuthorizeApp } from 'src/engine/core-modules/auth/dto/authorize-app.entity'; 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 { 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 { ChallengeInput } from 'src/engine/core-modules/auth/dto/challenge.input';
import { UpdatePassword } from 'src/engine/core-modules/auth/dto/update-password.entity'; import { UpdatePassword } from 'src/engine/core-modules/auth/dto/update-password.entity';
import { 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 { 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 { 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 { 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 { EmailService } from 'src/engine/core-modules/email/email.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.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 { 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 { 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 { 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 { WorkspaceAuthProvider } from 'src/engine/core-modules/workspace/types/workspace.type';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Injectable() @Injectable()
// eslint-disable-next-line @nx/workspace-inject-workspace-repository // eslint-disable-next-line @nx/workspace-inject-workspace-repository

View File

@ -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 { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity';
import { PasswordResetToken } from 'src/engine/core-modules/auth/dto/token.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 { 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 { EmailService } from 'src/engine/core-modules/email/email.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
@Injectable() @Injectable()
export class ResetPasswordService { export class ResetPasswordService {

View File

@ -6,6 +6,7 @@ import {
RawBodyRequest, RawBodyRequest,
Req, Req,
Res, Res,
UseFilters,
} from '@nestjs/common'; } from '@nestjs/common';
import { Response } from 'express'; import { Response } from 'express';
@ -15,11 +16,13 @@ import {
BillingExceptionCode, BillingExceptionCode,
} from 'src/engine/core-modules/billing/billing.exception'; } from 'src/engine/core-modules/billing/billing.exception';
import { WebhookEvent } from 'src/engine/core-modules/billing/enums/billing-webhook-events.enum'; 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 { 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 { 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 { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing/services/billing-webhook-subscription.service';
import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service'; import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service';
@Controller('billing') @Controller('billing')
@UseFilters(BillingRestApiExceptionFilter)
export class BillingController { export class BillingController {
protected readonly logger = new Logger(BillingController.name); protected readonly logger = new Logger(BillingController.name);

View File

@ -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 { 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 { 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 { 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 { 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 { BillingPortalWorkspaceService } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.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, BillingResolver,
BillingWorkspaceMemberListener, BillingWorkspaceMemberListener,
BillingService, BillingService,
BillingRestApiExceptionFilter,
], ],
exports: [ exports: [
BillingSubscriptionService, BillingSubscriptionService,

View File

@ -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<Response>();
switch (exception.code) {
case BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND:
return this.httpExceptionHandlerService.handleError(
exception,
response,
404,
);
default:
return this.httpExceptionHandlerService.handleError(
exception,
response,
500,
);
}
}
}

View File

@ -31,10 +31,13 @@ export class BillingPortalWorkspaceService {
priceId: string, priceId: string,
successUrlPath?: string, successUrlPath?: string,
): Promise<string> { ): Promise<string> {
const frontBaseUrl = this.domainManagerService.getBaseUrl().toString(); const frontBaseUrl = this.domainManagerService.getBaseUrl();
const successUrl = successUrlPath const cancelUrl = frontBaseUrl.toString();
? frontBaseUrl + successUrlPath
: frontBaseUrl; if (successUrlPath) {
frontBaseUrl.pathname = successUrlPath;
}
const successUrl = frontBaseUrl.toString();
const quantity = await this.userWorkspaceRepository.countBy({ const quantity = await this.userWorkspaceRepository.countBy({
workspaceId: workspace.id, workspaceId: workspace.id,
@ -51,7 +54,7 @@ export class BillingPortalWorkspaceService {
priceId, priceId,
quantity, quantity,
successUrl, successUrl,
frontBaseUrl, cancelUrl,
stripeCustomerId, stripeCustomerId,
); );
@ -81,10 +84,12 @@ export class BillingPortalWorkspaceService {
throw new Error('Error: missing stripeCustomerId'); throw new Error('Error: missing stripeCustomerId');
} }
const frontBaseUrl = this.domainManagerService.getBaseUrl().toString(); const frontBaseUrl = this.domainManagerService.getBaseUrl();
const returnUrl = returnUrlPath
? frontBaseUrl + returnUrlPath if (returnUrlPath) {
: frontBaseUrl; frontBaseUrl.pathname = returnUrlPath;
}
const returnUrl = frontBaseUrl.toString();
const session = await this.stripeService.createBillingPortalSession( const session = await this.stripeService.createBillingPortalSession(
stripeCustomerId, stripeCustomerId,

View File

@ -1,8 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
import { ClientConfigResolver } from './client-config.resolver'; import { ClientConfigResolver } from './client-config.resolver';
@Module({ @Module({
imports: [DomainManagerModule],
providers: [ClientConfigResolver], providers: [ClientConfigResolver],
}) })
export class ClientConfigModule {} export class ClientConfigModule {}

View File

@ -1,5 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing'; 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 { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { ClientConfigResolver } from './client-config.resolver'; import { ClientConfigResolver } from './client-config.resolver';
@ -15,6 +16,10 @@ describe('ClientConfigResolver', () => {
provide: EnvironmentService, provide: EnvironmentService,
useValue: {}, useValue: {},
}, },
{
provide: DomainManagerService,
useValue: {},
},
], ],
}).compile(); }).compile();

View File

@ -1,12 +1,16 @@
import { Query, Resolver } from '@nestjs/graphql'; 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 { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { ClientConfig } from './client-config.entity'; import { ClientConfig } from './client-config.entity';
@Resolver() @Resolver()
export class ClientConfigResolver { export class ClientConfigResolver {
constructor(private environmentService: EnvironmentService) {} constructor(
private environmentService: EnvironmentService,
private domainManagerService: DomainManagerService,
) {}
@Query(() => ClientConfig) @Query(() => ClientConfig)
async clientConfig(): Promise<ClientConfig> { async clientConfig(): Promise<ClientConfig> {
@ -24,7 +28,7 @@ export class ClientConfigResolver {
'IS_MULTIWORKSPACE_ENABLED', 'IS_MULTIWORKSPACE_ENABLED',
), ),
defaultSubdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'), defaultSubdomain: this.environmentService.get('DEFAULT_SUBDOMAIN'),
frontDomain: this.environmentService.get('FRONT_DOMAIN'), frontDomain: this.domainManagerService.getFrontUrl().hostname,
debugMode: this.environmentService.get('DEBUG_MODE'), debugMode: this.environmentService.get('DEBUG_MODE'),
support: { support: {
supportDriver: this.environmentService.get('SUPPORT_DRIVER'), supportDriver: this.environmentService.get('SUPPORT_DRIVER'),

View File

@ -1,12 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/service/domain-manager.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Module({ @Module({
imports: [NestjsQueryTypeOrmModule.forFeature([Workspace], 'core')], imports: [TypeOrmModule.forFeature([Workspace], 'core')],
providers: [DomainManagerService], providers: [DomainManagerService],
exports: [DomainManagerService], exports: [DomainManagerService],
}) })

View File

@ -21,11 +21,29 @@ export class DomainManagerService {
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
) {} ) {}
getBaseUrl() { getFrontUrl() {
const baseUrl = new URL( 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')}`, `${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 ( if (
this.environmentService.get('IS_MULTIWORKSPACE_ENABLED') && this.environmentService.get('IS_MULTIWORKSPACE_ENABLED') &&
this.environmentService.get('DEFAULT_SUBDOMAIN') this.environmentService.get('DEFAULT_SUBDOMAIN')
@ -33,10 +51,6 @@ export class DomainManagerService {
baseUrl.hostname = `${this.environmentService.get('DEFAULT_SUBDOMAIN')}.${baseUrl.hostname}`; 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; return baseUrl;
} }
@ -87,10 +101,9 @@ export class DomainManagerService {
getWorkspaceSubdomainByOrigin = (origin: string) => { getWorkspaceSubdomainByOrigin = (origin: string) => {
const { hostname: originHostname } = new URL(origin); const { hostname: originHostname } = new URL(origin);
const subdomain = originHostname.replace( const frontDomain = this.getFrontUrl().hostname;
`.${this.environmentService.get('FRONT_DOMAIN')}`,
'', const subdomain = originHostname.replace(`.${frontDomain}`, '');
);
if (this.isDefaultSubdomain(subdomain)) { if (this.isDefaultSubdomain(subdomain)) {
return; return;

View File

@ -129,7 +129,7 @@ export class EnvironmentVariables {
// Frontend URL // Frontend URL
@IsString() @IsString()
@IsOptional() @IsOptional()
FRONT_DOMAIN = 'localhost'; FRONT_DOMAIN?: string;
@IsString() @IsString()
@ValidateIf((env) => env.IS_MULTIWORKSPACE_ENABLED) @ValidateIf((env) => env.IS_MULTIWORKSPACE_ENABLED)
@ -137,12 +137,12 @@ export class EnvironmentVariables {
@IsString() @IsString()
@IsOptional() @IsOptional()
FRONT_PROTOCOL: 'http' | 'https' = 'http'; FRONT_PROTOCOL?: 'http' | 'https' = 'http';
@CastToPositiveNumber() @CastToPositiveNumber()
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
FRONT_PORT = 3001; FRONT_PORT?: number;
@IsUrl({ require_tld: false, require_protocol: true }) @IsUrl({ require_tld: false, require_protocol: true })
@IsOptional() @IsOptional()

View File

@ -9,12 +9,13 @@ import {
OPTIONS_TYPE, OPTIONS_TYPE,
} from 'src/engine/core-modules/exception-handler/exception-handler.module-definition'; } from 'src/engine/core-modules/exception-handler/exception-handler.module-definition';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; 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'; import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces';
@Global() @Global()
@Module({ @Module({
providers: [ExceptionHandlerService], providers: [ExceptionHandlerService, HttpExceptionHandlerService],
exports: [ExceptionHandlerService], exports: [ExceptionHandlerService, HttpExceptionHandlerService],
}) })
export class ExceptionHandlerModule extends ConfigurableModuleClass { export class ExceptionHandlerModule extends ConfigurableModuleClass {
static forRoot(options: typeof OPTIONS_TYPE): DynamicModule { static forRoot(options: typeof OPTIONS_TYPE): DynamicModule {

View File

@ -6,11 +6,11 @@ import { Response } from 'express';
import { ExceptionHandlerUser } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-user.interface'; 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 { 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 { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { CustomException } from 'src/utils/custom-exception';
export const handleException = ( export const handleException = (
exception: AuthException, exception: CustomException,
exceptionHandlerService: ExceptionHandlerService, exceptionHandlerService: ExceptionHandlerService,
user?: ExceptionHandlerUser, user?: ExceptionHandlerUser,
workspace?: ExceptionHandlerWorkspace, workspace?: ExceptionHandlerWorkspace,
@ -24,7 +24,7 @@ interface RequestAndParams {
} }
@Injectable({ scope: Scope.REQUEST }) @Injectable({ scope: Scope.REQUEST })
export class AuthExceptionHandlerService { export class HttpExceptionHandlerService {
constructor( constructor(
private readonly exceptionHandlerService: ExceptionHandlerService, private readonly exceptionHandlerService: ExceptionHandlerService,
@Inject(REQUEST) @Inject(REQUEST)
@ -32,7 +32,7 @@ export class AuthExceptionHandlerService {
) {} ) {}
handleError = ( handleError = (
exception: AuthException, exception: CustomException,
response: Response<any, Record<string, any>>, response: Response<any, Record<string, any>>,
errorCode?: number, errorCode?: number,
user?: ExceptionHandlerUser, user?: ExceptionHandlerUser,

View File

@ -4,15 +4,15 @@ import { NestExpressApplication } from '@nestjs/platform-express';
import fs from 'fs'; import fs from 'fs';
import session from 'express-session';
import bytes from 'bytes'; import bytes from 'bytes';
import { useContainer } from 'class-validator'; import { useContainer } from 'class-validator';
import session from 'express-session';
import { graphqlUploadExpress } from 'graphql-upload'; import { graphqlUploadExpress } from 'graphql-upload';
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 { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { LoggerService } from 'src/engine/core-modules/logger/logger.service';
import { getSessionStorageOptions } from 'src/engine/core-modules/session-storage/session-storage.module-factory';
import { UnhandledExceptionFilter } from 'src/utils/apply-cors-to-exceptions';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import './instrument'; import './instrument';
@ -48,7 +48,7 @@ const bootstrap = async () => {
// Use our logger // Use our logger
app.useLogger(logger); app.useLogger(logger);
app.useGlobalFilters(new ApplyCorsToExceptions()); app.useGlobalFilters(new UnhandledExceptionFilter());
// Apply validation pipes globally // Apply validation pipes globally
app.useGlobalPipes( app.useGlobalPipes(

View File

@ -11,7 +11,7 @@ import { Response } from 'express';
// the CORS headers are missing in the response. // the CORS headers are missing in the response.
// This class add CORS headers to exception response to avoid misleading CORS error // This class add CORS headers to exception response to avoid misleading CORS error
@Catch() @Catch()
export class ApplyCorsToExceptions implements ExceptionFilter { export class UnhandledExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) { catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp(); const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>(); const response = ctx.getResponse<Response>();
@ -20,6 +20,7 @@ export class ApplyCorsToExceptions implements ExceptionFilter {
return; return;
} }
// TODO: Check if needed, remove otherwise.
response.header('Access-Control-Allow-Origin', '*'); response.header('Access-Control-Allow-Origin', '*');
response.header( response.header(
'Access-Control-Allow-Methods', 'Access-Control-Allow-Methods',

View File

@ -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 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. 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 ### v0.32.0 to v0.33.0