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

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

View File

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

View File

@ -45,23 +45,20 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
if (!data.displayName || !data.displayName.length) {
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.userWorkspaceService.createWorkspaceMember(
user.defaultWorkspace.id,
user,
);
await this.workspaceRepository.update(user.defaultWorkspace.id, {
displayName: data.displayName,
activationStatus: WorkspaceActivationStatus.ACTIVE,
});
return user.defaultWorkspace;
}
async isWorkspaceActivated(id: string): Promise<boolean> {
return await this.workspaceManagerService.doesDataSourceExist(id);
}
async softDeleteWorkspace(id: string) {
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';
export enum WorkspaceActivationStatus {
PENDING_CREATION = 'PENDING_CREATION',
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
}

View File

@ -29,7 +29,7 @@ import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/worksp
import { assert } from 'src/utils/assert';
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';
@ -100,21 +100,6 @@ export class WorkspaceResolver {
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 })
async currentCacheVersion(
@Parent() workspace: Workspace,