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:
@ -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 = {
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -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"],
|
||||||
|
|||||||
@ -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'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 });
|
||||||
|
|
||||||
|
|||||||
@ -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',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user