Leverage workspace activationStatus to decide if a workspace is activated or not (#6497)

An ACTIVE workspace is a workspace that has a complete workspaceSchema
and is authorized to be browsed by users.

In this PR, I'm:
- introducing a new activationStatus: PENDING_CREATION (existing ACTIVE
/ INACTIVE)
- removing workspaceService.isWorkspaceActivated (based on
workspaceSchema existence which is not robust and checking
activationStatus.ACTIVE instead)
- removing dynamic activationStatus field on worksapce resolver (we can
use the postgres column directly now that data has been migrated)
- on user sign up creating the workspace in PENDING_CREATION, and on
workspace activation setting it to ACTIVE
- only re-activating a workspace if the current activationStatus is
INACTIVE through billing webhooks (a PENDING_CREATION should stay
PENDING and ACTIVE should stay ACTIVE)
This commit is contained in:
Charles Bochet
2024-08-01 17:05:15 +02:00
committed by GitHub
parent 1a90df8961
commit 5c92ab937e
12 changed files with 217 additions and 50 deletions

View File

@ -1054,7 +1054,7 @@ export type ServerlessFunctionExecutionResult = {
status: ServerlessFunctionExecutionStatus; status: ServerlessFunctionExecutionStatus;
}; };
/** Status of the table */ /** Status of the serverless function execution */
export enum ServerlessFunctionExecutionStatus { export enum ServerlessFunctionExecutionStatus {
Error = 'ERROR', Error = 'ERROR',
Success = 'SUCCESS' Success = 'SUCCESS'
@ -1397,7 +1397,8 @@ export type WorkspaceFeatureFlagsArgs = {
export enum WorkspaceActivationStatus { export enum WorkspaceActivationStatus {
Active = 'ACTIVE', Active = 'ACTIVE',
Inactive = 'INACTIVE' Inactive = 'INACTIVE',
PendingCreation = 'PENDING_CREATION'
} }
export type WorkspaceEdge = { export type WorkspaceEdge = {

View File

@ -162,6 +162,17 @@ export type ClientConfig = {
telemetry: Telemetry; telemetry: Telemetry;
}; };
export type CreateServerlessFunctionFromFileInput = {
description?: InputMaybe<Scalars['String']>;
name: Scalars['String'];
};
export type CreateServerlessFunctionInput = {
code: Scalars['String'];
description?: InputMaybe<Scalars['String']>;
name: Scalars['String'];
};
export type CursorPaging = { export type CursorPaging = {
/** Paginate after opaque cursor */ /** Paginate after opaque cursor */
after?: InputMaybe<Scalars['ConnectionCursor']>; after?: InputMaybe<Scalars['ConnectionCursor']>;
@ -178,6 +189,11 @@ export type DeleteOneObjectInput = {
id: Scalars['UUID']; id: Scalars['UUID'];
}; };
export type DeleteServerlessFunctionInput = {
/** The id of the function. */
id: Scalars['ID'];
};
/** Schema update on a table */ /** Schema update on a table */
export enum DistantTableUpdate { export enum DistantTableUpdate {
ColumnsAdded = 'COLUMNS_ADDED', ColumnsAdded = 'COLUMNS_ADDED',
@ -310,14 +326,18 @@ export type Mutation = {
checkoutSession: SessionEntity; checkoutSession: SessionEntity;
createOneAppToken: AppToken; createOneAppToken: AppToken;
createOneObject: Object; createOneObject: Object;
createOneServerlessFunction: ServerlessFunction;
createOneServerlessFunctionFromFile: ServerlessFunction;
deleteCurrentWorkspace: Workspace; deleteCurrentWorkspace: Workspace;
deleteOneObject: Object; deleteOneObject: Object;
deleteOneServerlessFunction: ServerlessFunction;
deleteUser: User; deleteUser: User;
disablePostgresProxy: PostgresCredentials; disablePostgresProxy: PostgresCredentials;
emailPasswordResetLink: EmailPasswordResetLink; emailPasswordResetLink: EmailPasswordResetLink;
enablePostgresProxy: PostgresCredentials; enablePostgresProxy: PostgresCredentials;
enableWorkflowTrigger: Scalars['Boolean']; enableWorkflowTrigger: Scalars['Boolean'];
exchangeAuthorizationCode: ExchangeAuthCode; exchangeAuthorizationCode: ExchangeAuthCode;
executeOneServerlessFunction: ServerlessFunctionExecutionResult;
generateApiKeyToken: ApiKeyToken; generateApiKeyToken: ApiKeyToken;
generateJWT: AuthTokens; generateJWT: AuthTokens;
generateTransientToken: TransientToken; generateTransientToken: TransientToken;
@ -327,8 +347,10 @@ export type Mutation = {
signUp: LoginToken; signUp: LoginToken;
skipSyncEmailOnboardingStep: OnboardingStepSuccess; skipSyncEmailOnboardingStep: OnboardingStepSuccess;
track: Analytics; track: Analytics;
triggerWorkflow: WorkflowTriggerResult;
updateBillingSubscription: UpdateBillingEntity; updateBillingSubscription: UpdateBillingEntity;
updateOneObject: Object; updateOneObject: Object;
updateOneServerlessFunction: ServerlessFunction;
updatePasswordViaResetToken: InvalidatePassword; updatePasswordViaResetToken: InvalidatePassword;
updateWorkspace: Workspace; updateWorkspace: Workspace;
uploadFile: Scalars['String']; uploadFile: Scalars['String'];
@ -369,11 +391,27 @@ export type MutationCheckoutSessionArgs = {
}; };
export type MutationCreateOneServerlessFunctionArgs = {
input: CreateServerlessFunctionInput;
};
export type MutationCreateOneServerlessFunctionFromFileArgs = {
file: Scalars['Upload'];
input: CreateServerlessFunctionFromFileInput;
};
export type MutationDeleteOneObjectArgs = { export type MutationDeleteOneObjectArgs = {
input: DeleteOneObjectInput; input: DeleteOneObjectInput;
}; };
export type MutationDeleteOneServerlessFunctionArgs = {
input: DeleteServerlessFunctionInput;
};
export type MutationEmailPasswordResetLinkArgs = { export type MutationEmailPasswordResetLinkArgs = {
email: Scalars['String']; email: Scalars['String'];
}; };
@ -391,6 +429,12 @@ export type MutationExchangeAuthorizationCodeArgs = {
}; };
export type MutationExecuteOneServerlessFunctionArgs = {
id: Scalars['UUID'];
payload?: InputMaybe<Scalars['JSON']>;
};
export type MutationGenerateApiKeyTokenArgs = { export type MutationGenerateApiKeyTokenArgs = {
apiKeyId: Scalars['String']; apiKeyId: Scalars['String'];
expiresAt: Scalars['String']; expiresAt: Scalars['String'];
@ -431,11 +475,21 @@ export type MutationTrackArgs = {
}; };
export type MutationTriggerWorkflowArgs = {
workflowVersionId: Scalars['String'];
};
export type MutationUpdateOneObjectArgs = { export type MutationUpdateOneObjectArgs = {
input: UpdateOneObjectInput; input: UpdateOneObjectInput;
}; };
export type MutationUpdateOneServerlessFunctionArgs = {
input: UpdateServerlessFunctionInput;
};
export type MutationUpdatePasswordViaResetTokenArgs = { export type MutationUpdatePasswordViaResetTokenArgs = {
newPassword: Scalars['String']; newPassword: Scalars['String'];
passwordResetToken: Scalars['String']; passwordResetToken: Scalars['String'];
@ -557,6 +611,8 @@ export type Query = {
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
object: Object; object: Object;
objects: ObjectConnection; objects: ObjectConnection;
serverlessFunction: ServerlessFunction;
serverlessFunctions: ServerlessFunctionConnection;
validatePasswordResetToken: ValidatePasswordResetToken; validatePasswordResetToken: ValidatePasswordResetToken;
}; };
@ -731,9 +787,21 @@ export type ServerlessFunctionEdge = {
export type ServerlessFunctionExecutionResult = { export type ServerlessFunctionExecutionResult = {
__typename?: 'ServerlessFunctionExecutionResult'; __typename?: 'ServerlessFunctionExecutionResult';
/** Execution result in JSON format */ /** Execution result in JSON format */
result: Scalars['JSON']; data?: Maybe<Scalars['JSON']>;
/** Execution duration in milliseconds */
duration: Scalars['Float'];
/** Execution error in JSON format */
error?: Maybe<Scalars['JSON']>;
/** Execution status */
status: ServerlessFunctionExecutionStatus;
}; };
/** Status of the serverless function execution */
export enum ServerlessFunctionExecutionStatus {
Error = 'ERROR',
Success = 'SUCCESS'
}
/** SyncStatus of the serverlessFunction */ /** SyncStatus of the serverlessFunction */
export enum ServerlessFunctionSyncStatus { export enum ServerlessFunctionSyncStatus {
NotReady = 'NOT_READY', NotReady = 'NOT_READY',
@ -896,6 +964,14 @@ export type UpdateOneObjectInput = {
update: UpdateObjectPayload; update: UpdateObjectPayload;
}; };
export type UpdateServerlessFunctionInput = {
code: Scalars['String'];
description?: InputMaybe<Scalars['String']>;
/** Id of the serverless function to execute */
id: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateWorkspaceInput = { export type UpdateWorkspaceInput = {
allowImpersonation?: InputMaybe<Scalars['Boolean']>; allowImpersonation?: InputMaybe<Scalars['Boolean']>;
displayName?: InputMaybe<Scalars['String']>; displayName?: InputMaybe<Scalars['String']>;
@ -969,6 +1045,12 @@ export type Verify = {
user: User; user: User;
}; };
export type WorkflowTriggerResult = {
__typename?: 'WorkflowTriggerResult';
/** Execution result in JSON format */
result?: Maybe<Scalars['JSON']>;
};
export type Workspace = { export type Workspace = {
__typename?: 'Workspace'; __typename?: 'Workspace';
activationStatus: WorkspaceActivationStatus; activationStatus: WorkspaceActivationStatus;
@ -1002,7 +1084,8 @@ export type WorkspaceFeatureFlagsArgs = {
export enum WorkspaceActivationStatus { export enum WorkspaceActivationStatus {
Active = 'ACTIVE', Active = 'ACTIVE',
Inactive = 'INACTIVE' Inactive = 'INACTIVE',
PendingCreation = 'PENDING_CREATION'
} }
export type WorkspaceEdge = { export type WorkspaceEdge = {

View File

@ -128,6 +128,18 @@
"parallel": false "parallel": false
} }
}, },
"database:migrate:revert": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
"options": {
"cwd": "packages/twenty-server",
"commands": [
"nx typeorm -- migration:revert -d src/database/typeorm/metadata/metadata.datasource",
"nx typeorm -- migration:revert -d src/database/typeorm/core/core.datasource"
],
"parallel": false
}
},
"database:reset": { "database:reset": {
"executor": "nx:run-commands", "executor": "nx:run-commands",
"dependsOn": ["build"], "dependsOn": ["build"],

View File

@ -0,0 +1,67 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateActivationStatus1722256203540 implements MigrationInterface {
name = 'UpdateActivationStatus1722256203540';
public async up(queryRunner: QueryRunner): Promise<void> {
// Set current column as text
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE text USING "activationStatus"::text`,
);
// Drop default value
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" DROP DEFAULT`,
);
// Drop the old enum type
await queryRunner.query(
`DROP TYPE "core"."workspace_activationstatus_enum"`,
);
await queryRunner.query(
`CREATE TYPE "core"."workspace_activationStatus_enum" AS ENUM('PENDING_CREATION', 'ACTIVE', 'INACTIVE')`,
);
// Re-apply the enum type
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE "core"."workspace_activationStatus_enum" USING "activationStatus"::"core"."workspace_activationStatus_enum"`,
);
// Update default value
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DEFAULT 'INACTIVE'`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Set current column as text
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE text USING "activationStatus"::text`,
);
// Drop default value
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" DROP DEFAULT`,
);
// Drop the old enum type
await queryRunner.query(
`DROP TYPE "core"."workspace_activationStatus_enum"`,
);
await queryRunner.query(
`CREATE TYPE "core"."workspace_activationstatus_enum" AS ENUM('ACTIVE', 'INACTIVE')`,
);
// Re-apply the enum type
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DATA TYPE "core"."workspace_activationstatus_enum" USING "activationStatus"::"core"."workspace_activationstatus_enum"`,
);
// Update default value
await queryRunner.query(
`ALTER TABLE "core"."workspace" ALTER COLUMN "activationStatus" SET DEFAULT 'INACTIVE'`,
);
}
}

View File

@ -21,5 +21,13 @@ export class AddRuntimeColumnToServerlessFunction1721309629608
await queryRunner.query( await queryRunner.query(
`ALTER TABLE "metadata"."serverlessFunction" DROP COLUMN "runtime"`, `ALTER TABLE "metadata"."serverlessFunction" DROP COLUMN "runtime"`,
); );
await queryRunner.query(
`ALTER TABLE "metadata"."serverlessFunction" DROP COLUMN "description"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."serverlessFunction" DROP COLUMN "sourceCodeFullPath"`,
);
} }
} }

View File

@ -1,30 +1,33 @@
import { HttpService } from '@nestjs/axios';
import { import {
BadRequestException, BadRequestException,
ForbiddenException, ForbiddenException,
Injectable, Injectable,
} from '@nestjs/common'; } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { HttpService } from '@nestjs/axios';
import FileType from 'file-type';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import FileType from 'file-type';
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
import { assert } from 'src/utils/assert';
import { import {
PASSWORD_REGEX, PASSWORD_REGEX,
hashPassword,
compareHash, compareHash,
hashPassword,
} from 'src/engine/core-modules/auth/auth.util'; } from 'src/engine/core-modules/auth/auth.util';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service'; import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { getImageBufferFromUrl } from 'src/utils/image';
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 { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service'; import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
import {
Workspace,
WorkspaceActivationStatus,
} from 'src/engine/core-modules/workspace/workspace.entity';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { assert } from 'src/utils/assert';
import { getImageBufferFromUrl } from 'src/utils/image';
export type SignInUpServiceInput = { export type SignInUpServiceInput = {
email: string; email: string;
@ -144,11 +147,8 @@ export class SignInUpService {
ForbiddenException, ForbiddenException,
); );
const isWorkspaceActivated =
await this.workspaceService.isWorkspaceActivated(workspace.id);
assert( assert(
isWorkspaceActivated, workspace.activationStatus === WorkspaceActivationStatus.ACTIVE,
'Workspace is not ready to welcome new members', 'Workspace is not ready to welcome new members',
ForbiddenException, ForbiddenException,
); );
@ -203,6 +203,7 @@ export class SignInUpService {
displayName: '', displayName: '',
domainName: '', domainName: '',
inviteHash: v4(), inviteHash: v4(),
activationStatus: WorkspaceActivationStatus.PENDING_CREATION,
}); });
const workspace = await this.workspaceRepository.save(workspaceToCreate); const workspace = await this.workspaceRepository.save(workspaceToCreate);

View File

@ -284,7 +284,7 @@ export class BillingWorkspaceService {
| Stripe.CustomerSubscriptionCreatedEvent.Data | Stripe.CustomerSubscriptionCreatedEvent.Data
| Stripe.CustomerSubscriptionDeletedEvent.Data, | Stripe.CustomerSubscriptionDeletedEvent.Data,
) { ) {
const workspace = this.workspaceRepository.find({ const workspace = await this.workspaceRepository.findOne({
where: { id: workspaceId }, where: { id: workspaceId },
}); });
@ -341,9 +341,10 @@ export class BillingWorkspaceService {
} }
if ( if (
data.object.status === SubscriptionStatus.Active || (data.object.status === SubscriptionStatus.Active ||
data.object.status === SubscriptionStatus.Trialing || data.object.status === SubscriptionStatus.Trialing ||
data.object.status === SubscriptionStatus.PastDue data.object.status === SubscriptionStatus.PastDue) &&
workspace.activationStatus == WorkspaceActivationStatus.INACTIVE
) { ) {
await this.workspaceRepository.update(workspaceId, { await this.workspaceRepository.update(workspaceId, {
activationStatus: WorkspaceActivationStatus.ACTIVE, activationStatus: WorkspaceActivationStatus.ACTIVE,

View File

@ -45,23 +45,20 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
if (!data.displayName || !data.displayName.length) { if (!data.displayName || !data.displayName.length) {
throw new BadRequestException("'displayName' not provided"); throw new BadRequestException("'displayName' not provided");
} }
await this.workspaceRepository.update(user.defaultWorkspace.id, {
displayName: data.displayName,
activationStatus: WorkspaceActivationStatus.ACTIVE,
});
await this.workspaceManagerService.init(user.defaultWorkspace.id); await this.workspaceManagerService.init(user.defaultWorkspace.id);
await this.userWorkspaceService.createWorkspaceMember( await this.userWorkspaceService.createWorkspaceMember(
user.defaultWorkspace.id, user.defaultWorkspace.id,
user, user,
); );
await this.workspaceRepository.update(user.defaultWorkspace.id, {
displayName: data.displayName,
activationStatus: WorkspaceActivationStatus.ACTIVE,
});
return user.defaultWorkspace; return user.defaultWorkspace;
} }
async isWorkspaceActivated(id: string): Promise<boolean> {
return await this.workspaceManagerService.doesDataSourceExist(id);
}
async softDeleteWorkspace(id: string) { async softDeleteWorkspace(id: string) {
const workspace = await this.workspaceRepository.findOneBy({ id }); const workspace = await this.workspaceRepository.findOneBy({ id });

View File

@ -21,6 +21,7 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
export enum WorkspaceActivationStatus { export enum WorkspaceActivationStatus {
PENDING_CREATION = 'PENDING_CREATION',
ACTIVE = 'ACTIVE', ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE', INACTIVE = 'INACTIVE',
} }

View File

@ -29,7 +29,7 @@ import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/worksp
import { assert } from 'src/utils/assert'; import { assert } from 'src/utils/assert';
import { streamToBuffer } from 'src/utils/stream-to-buffer'; import { streamToBuffer } from 'src/utils/stream-to-buffer';
import { Workspace, WorkspaceActivationStatus } from './workspace.entity'; import { Workspace } from './workspace.entity';
import { WorkspaceService } from './services/workspace.service'; import { WorkspaceService } from './services/workspace.service';
@ -100,21 +100,6 @@ export class WorkspaceResolver {
return this.workspaceService.deleteWorkspace(id); return this.workspaceService.deleteWorkspace(id);
} }
@ResolveField(() => WorkspaceActivationStatus)
async activationStatus(
@Parent() workspace: Workspace,
): Promise<WorkspaceActivationStatus> {
if (workspace.activationStatus) {
return workspace.activationStatus;
}
if (await this.workspaceService.isWorkspaceActivated(workspace.id)) {
return WorkspaceActivationStatus.ACTIVE;
}
return WorkspaceActivationStatus.INACTIVE;
}
@ResolveField(() => String, { nullable: true }) @ResolveField(() => String, { nullable: true })
async currentCacheVersion( async currentCacheVersion(
@Parent() workspace: Workspace, @Parent() workspace: Workspace,

View File

@ -1,10 +1,12 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { CalendarCreateCompanyAndContactAfterSyncJob } from 'src/modules/calendar/calendar-event-participant-manager/jobs/calendar-create-company-and-contact-after-sync.job'; import { CalendarCreateCompanyAndContactAfterSyncJob } from 'src/modules/calendar/calendar-event-participant-manager/jobs/calendar-create-company-and-contact-after-sync.job';
@ -17,18 +19,17 @@ import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-e
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
import { ContactCreationManagerModule } from 'src/modules/contact-creation-manager/contact-creation-manager.module'; import { ContactCreationManagerModule } from 'src/modules/contact-creation-manager/contact-creation-manager.module';
import { MatchParticipantModule } from 'src/modules/match-participant/match-participant.module'; import { MatchParticipantModule } from 'src/modules/match-participant/match-participant.module';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
@Module({ @Module({
imports: [ imports: [
WorkspaceDataSourceModule, WorkspaceDataSourceModule,
WorkspaceModule, WorkspaceModule,
TwentyORMModule.forFeature([CalendarEventParticipantWorkspaceEntity]), TwentyORMModule.forFeature([CalendarEventParticipantWorkspaceEntity]),
ObjectMetadataRepositoryModule.forFeature([PersonWorkspaceEntity]),
TypeOrmModule.forFeature( TypeOrmModule.forFeature(
[ObjectMetadataEntity, FieldMetadataEntity], [ObjectMetadataEntity, FieldMetadataEntity],
'metadata', 'metadata',
), ),
NestjsQueryTypeOrmModule.forFeature([Workspace], 'core'),
ContactCreationManagerModule, ContactCreationManagerModule,
MatchParticipantModule, MatchParticipantModule,
], ],

View File

@ -1,6 +1,9 @@
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service'; import { Repository } from 'typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator'; import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -20,7 +23,8 @@ export type CalendarEventParticipantMatchParticipantJobData = {
}) })
export class CalendarEventParticipantMatchParticipantJob { export class CalendarEventParticipantMatchParticipantJob {
constructor( constructor(
private readonly workspaceService: WorkspaceService, @InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
private readonly matchParticipantService: MatchParticipantService<CalendarEventParticipantWorkspaceEntity>, private readonly matchParticipantService: MatchParticipantService<CalendarEventParticipantWorkspaceEntity>,
) {} ) {}
@ -30,7 +34,13 @@ export class CalendarEventParticipantMatchParticipantJob {
): Promise<void> { ): Promise<void> {
const { workspaceId, email, personId, workspaceMemberId } = data; const { workspaceId, email, personId, workspaceMemberId } = data;
if (!this.workspaceService.isWorkspaceActivated(workspaceId)) { const workspace = await this.workspaceRepository.findOne({
where: {
id: workspaceId,
},
});
if (workspace?.activationStatus !== 'ACTIVE') {
return; return;
} }