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
27 changed files with 141 additions and 76 deletions

View File

@ -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,
],

View File

@ -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,

View File

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

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 { 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 {

View File

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

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 { 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,

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,
successUrlPath?: string,
): Promise<string> {
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,

View File

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

View File

@ -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();

View File

@ -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<ClientConfig> {
@ -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'),

View File

@ -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],
})

View File

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

View File

@ -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()

View File

@ -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 {

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 { 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<any, Record<string, any>>,
errorCode?: number,
user?: ExceptionHandlerUser,