5188 bug some canceled subscriptions are billed (#5254)
When user is deleting its account on a specific workspace, we remove it as if it was a workspaceMember, and if no workspaceMember remains, we delete the workspace and the associated stripe subscription
This commit is contained in:
@ -196,7 +196,10 @@ export class BillingService {
|
|||||||
? frontBaseUrl + successUrlPath
|
? frontBaseUrl + successUrlPath
|
||||||
: frontBaseUrl;
|
: frontBaseUrl;
|
||||||
|
|
||||||
let quantity = 1;
|
const quantity =
|
||||||
|
(await this.userWorkspaceService.getWorkspaceMemberCount(
|
||||||
|
user.defaultWorkspaceId,
|
||||||
|
)) || 1;
|
||||||
|
|
||||||
const stripeCustomerId = (
|
const stripeCustomerId = (
|
||||||
await this.billingSubscriptionRepository.findOneBy({
|
await this.billingSubscriptionRepository.findOneBy({
|
||||||
@ -204,16 +207,6 @@ export class BillingService {
|
|||||||
})
|
})
|
||||||
)?.stripeCustomerId;
|
)?.stripeCustomerId;
|
||||||
|
|
||||||
try {
|
|
||||||
quantity = await this.userWorkspaceService.getWorkspaceMemberCount(
|
|
||||||
user.defaultWorkspaceId,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.error(
|
|
||||||
`Failed to get workspace member count for workspace ${user.defaultWorkspaceId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await this.stripeService.createCheckoutSession(
|
const session = await this.stripeService.createCheckoutSession(
|
||||||
user,
|
user,
|
||||||
priceId,
|
priceId,
|
||||||
@ -260,6 +253,26 @@ export class BillingService {
|
|||||||
| Stripe.CustomerSubscriptionCreatedEvent.Data
|
| Stripe.CustomerSubscriptionCreatedEvent.Data
|
||||||
| Stripe.CustomerSubscriptionDeletedEvent.Data,
|
| Stripe.CustomerSubscriptionDeletedEvent.Data,
|
||||||
) {
|
) {
|
||||||
|
const workspace = this.workspaceRepository.find({
|
||||||
|
where: { id: workspaceId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workspaceRepository.update(workspaceId, {
|
||||||
|
subscriptionStatus: data.object.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
const billingSubscription = await this.getCurrentBillingSubscription({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!billingSubscription) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.billingSubscriptionRepository.upsert(
|
await this.billingSubscriptionRepository.upsert(
|
||||||
{
|
{
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
@ -274,18 +287,6 @@ export class BillingService {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.workspaceRepository.update(workspaceId, {
|
|
||||||
subscriptionStatus: data.object.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
const billingSubscription = await this.getCurrentBillingSubscription({
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!billingSubscription) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.billingSubscriptionItemRepository.upsert(
|
await this.billingSubscriptionItemRepository.upsert(
|
||||||
data.object.items.data.map((item) => {
|
data.object.items.data.map((item) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export class UpdateSubscriptionJob
|
|||||||
const workspaceMembersCount =
|
const workspaceMembersCount =
|
||||||
await this.userWorkspaceService.getWorkspaceMemberCount(data.workspaceId);
|
await this.userWorkspaceService.getWorkspaceMemberCount(data.workspaceId);
|
||||||
|
|
||||||
if (workspaceMembersCount <= 0) {
|
if (!workspaceMembersCount || workspaceMembersCount <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -70,17 +70,23 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
|||||||
this.eventEmitter.emit('workspaceMember.created', payload);
|
this.eventEmitter.emit('workspaceMember.created', payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getWorkspaceMemberCount(workspaceId: string): Promise<number> {
|
public async getWorkspaceMemberCount(
|
||||||
const dataSourceSchema =
|
workspaceId: string,
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
): Promise<number | undefined> {
|
||||||
|
try {
|
||||||
|
const dataSourceSchema =
|
||||||
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
await this.workspaceDataSourceService.executeRawQuery(
|
await this.workspaceDataSourceService.executeRawQuery(
|
||||||
`SELECT * FROM ${dataSourceSchema}."workspaceMember"`,
|
`SELECT * FROM ${dataSourceSchema}."workspaceMember"`,
|
||||||
[],
|
[],
|
||||||
workspaceId,
|
workspaceId,
|
||||||
)
|
)
|
||||||
).length;
|
).length;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkUserWorkspaceExists(
|
async checkUserWorkspaceExists(
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
|
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||||
|
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
@ -31,6 +33,14 @@ describe('UserService', () => {
|
|||||||
provide: TypeORMService,
|
provide: TypeORMService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: EventEmitter2,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
@ -8,17 +9,20 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
|||||||
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
|
||||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||||
|
import { WorkspaceMemberObjectMetadata } from 'src/modules/workspace-member/standard-objects/workspace-member.object-metadata';
|
||||||
|
import { ObjectRecordDeleteEvent } from 'src/engine/integrations/event-emitter/types/object-record-delete.event';
|
||||||
|
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||||
|
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||||
|
|
||||||
export class UserService extends TypeOrmQueryService<User> {
|
export class UserService extends TypeOrmQueryService<User> {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
@InjectRepository(UserWorkspace, 'core')
|
|
||||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
private readonly typeORMService: TypeORMService,
|
private readonly typeORMService: TypeORMService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly workspaceService: WorkspaceService,
|
||||||
) {
|
) {
|
||||||
super(userRepository);
|
super(userRepository);
|
||||||
}
|
}
|
||||||
@ -95,64 +99,46 @@ export class UserService extends TypeOrmQueryService<User> {
|
|||||||
|
|
||||||
assert(user, 'User not found');
|
assert(user, 'User not found');
|
||||||
|
|
||||||
|
const workspaceId = user.defaultWorkspaceId;
|
||||||
|
|
||||||
const dataSourceMetadata =
|
const dataSourceMetadata =
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
user.defaultWorkspace.id,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource =
|
const workspaceDataSource =
|
||||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
|
|
||||||
|
const workspaceMembers = await workspaceDataSource?.query(
|
||||||
|
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember"`,
|
||||||
|
);
|
||||||
|
const workspaceMember = workspaceMembers.filter(
|
||||||
|
(member: ObjectRecord<WorkspaceMemberObjectMetadata>) =>
|
||||||
|
member.userId === userId,
|
||||||
|
)?.[0];
|
||||||
|
|
||||||
|
assert(workspaceMember, 'WorkspaceMember not found');
|
||||||
|
|
||||||
|
if (workspaceMembers.length === 1) {
|
||||||
|
await this.workspaceService.deleteWorkspace(workspaceId);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
await workspaceDataSource?.query(
|
await workspaceDataSource?.query(
|
||||||
`DELETE FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${userId}'`,
|
`DELETE FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${userId}'`,
|
||||||
);
|
);
|
||||||
|
const payload =
|
||||||
|
new ObjectRecordDeleteEvent<WorkspaceMemberObjectMetadata>();
|
||||||
|
|
||||||
await this.userWorkspaceRepository.delete({ userId });
|
payload.workspaceId = workspaceId;
|
||||||
|
payload.properties = {
|
||||||
|
before: workspaceMember,
|
||||||
|
};
|
||||||
|
payload.recordId = workspaceMember.id;
|
||||||
|
|
||||||
await this.userRepository.delete(user.id);
|
this.eventEmitter.emit('workspaceMember.deleted', payload);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleRemoveWorkspaceMember(workspaceId: string, userId: string) {
|
|
||||||
await this.userWorkspaceRepository.delete({
|
|
||||||
userId,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
await this.reassignOrRemoveUserDefaultWorkspace(workspaceId, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async reassignOrRemoveUserDefaultWorkspace(
|
|
||||||
workspaceId: string,
|
|
||||||
userId: string,
|
|
||||||
) {
|
|
||||||
const userWorkspaces = await this.userWorkspaceRepository.find({
|
|
||||||
where: { userId: userId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userWorkspaces.length === 0) {
|
|
||||||
await this.userRepository.delete({ id: userId });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await this.userRepository.findOne({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new Error(`User ${userId} not found in workspace ${workspaceId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.defaultWorkspaceId === workspaceId) {
|
|
||||||
await this.userRepository.update(
|
|
||||||
{ id: userId },
|
|
||||||
{
|
|
||||||
defaultWorkspaceId: userWorkspaces[0].workspaceId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,8 @@ import { UserResolver } from 'src/engine/core-modules/user/user.resolver';
|
|||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
|
||||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||||
|
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
||||||
|
|
||||||
import { userAutoResolverOpts } from './user.auto-resolver-opts';
|
import { userAutoResolverOpts } from './user.auto-resolver-opts';
|
||||||
|
|
||||||
@ -21,14 +20,14 @@ import { UserService } from './services/user.service';
|
|||||||
imports: [
|
imports: [
|
||||||
NestjsQueryGraphQLModule.forFeature({
|
NestjsQueryGraphQLModule.forFeature({
|
||||||
imports: [
|
imports: [
|
||||||
NestjsQueryTypeOrmModule.forFeature([User, UserWorkspace], 'core'),
|
NestjsQueryTypeOrmModule.forFeature([User], 'core'),
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
],
|
],
|
||||||
resolvers: userAutoResolverOpts,
|
resolvers: userAutoResolverOpts,
|
||||||
}),
|
}),
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
FileUploadModule,
|
FileUploadModule,
|
||||||
UserWorkspaceModule,
|
WorkspaceModule,
|
||||||
],
|
],
|
||||||
exports: [UserService],
|
exports: [UserService],
|
||||||
providers: [UserService, UserResolver, TypeORMService],
|
providers: [UserService, UserResolver, TypeORMService],
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface';
|
import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||||
|
|
||||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||||
|
|
||||||
export type HandleWorkspaceMemberDeletedJobData = {
|
export type HandleWorkspaceMemberDeletedJobData = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -12,11 +12,14 @@ export type HandleWorkspaceMemberDeletedJobData = {
|
|||||||
export class HandleWorkspaceMemberDeletedJob
|
export class HandleWorkspaceMemberDeletedJob
|
||||||
implements MessageQueueJob<HandleWorkspaceMemberDeletedJobData>
|
implements MessageQueueJob<HandleWorkspaceMemberDeletedJobData>
|
||||||
{
|
{
|
||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly workspaceService: WorkspaceService) {}
|
||||||
|
|
||||||
async handle(data: HandleWorkspaceMemberDeletedJobData): Promise<void> {
|
async handle(data: HandleWorkspaceMemberDeletedJobData): Promise<void> {
|
||||||
const { workspaceId, userId } = data;
|
const { workspaceId, userId } = data;
|
||||||
|
|
||||||
await this.userService.handleRemoveWorkspaceMember(workspaceId, userId);
|
await this.workspaceService.handleRemoveWorkspaceMember(
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,18 +13,18 @@ import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/a
|
|||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-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 { BillingService } from 'src/engine/core-modules/billing/billing.service';
|
import { BillingService } from 'src/engine/core-modules/billing/billing.service';
|
||||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
|
||||||
|
|
||||||
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
private readonly workspaceRepository: Repository<Workspace>,
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
@InjectRepository(User, 'core')
|
||||||
|
private readonly userRepository: Repository<User>,
|
||||||
@InjectRepository(UserWorkspace, 'core')
|
@InjectRepository(UserWorkspace, 'core')
|
||||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||||
private readonly workspaceManagerService: WorkspaceManagerService,
|
private readonly workspaceManagerService: WorkspaceManagerService,
|
||||||
private readonly userWorkspaceService: UserWorkspaceService,
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
private readonly billingService: BillingService,
|
private readonly billingService: BillingService,
|
||||||
private readonly userService: UserService,
|
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository);
|
super(workspaceRepository);
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
return await this.workspaceManagerService.doesDataSourceExist(id);
|
return await this.workspaceManagerService.doesDataSourceExist(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async solfDeleteWorkspace(id: string) {
|
async softDeleteWorkspace(id: string) {
|
||||||
const workspace = await this.workspaceRepository.findOneBy({ id });
|
const workspace = await this.workspaceRepository.findOneBy({ id });
|
||||||
|
|
||||||
assert(workspace, 'Workspace not found');
|
assert(workspace, 'Workspace not found');
|
||||||
@ -67,14 +67,12 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
workspaceId: id,
|
workspaceId: id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspace = await this.solfDeleteWorkspace(id);
|
const workspace = await this.softDeleteWorkspace(id);
|
||||||
|
|
||||||
for (const userWorkspace of userWorkspaces) {
|
for (const userWorkspace of userWorkspaces) {
|
||||||
await this.userService.handleRemoveWorkspaceMember(
|
await this.handleRemoveWorkspaceMember(id, userWorkspace.userId);
|
||||||
id,
|
|
||||||
userWorkspace.userId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.workspaceRepository.delete(id);
|
await this.workspaceRepository.delete(id);
|
||||||
|
|
||||||
return workspace;
|
return workspace;
|
||||||
@ -85,4 +83,46 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
.find()
|
.find()
|
||||||
.then((workspaces) => workspaces.map((workspace) => workspace.id));
|
.then((workspaces) => workspaces.map((workspace) => workspace.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleRemoveWorkspaceMember(workspaceId: string, userId: string) {
|
||||||
|
await this.userWorkspaceRepository.delete({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
await this.reassignOrRemoveUserDefaultWorkspace(workspaceId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async reassignOrRemoveUserDefaultWorkspace(
|
||||||
|
workspaceId: string,
|
||||||
|
userId: string,
|
||||||
|
) {
|
||||||
|
const userWorkspaces = await this.userWorkspaceRepository.find({
|
||||||
|
where: { userId: userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userWorkspaces.length === 0) {
|
||||||
|
await this.userRepository.delete({ id: userId });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.userRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error(`User ${userId} not found in workspace ${workspaceId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.defaultWorkspaceId === workspaceId) {
|
||||||
|
await this.userRepository.update(
|
||||||
|
{ id: userId },
|
||||||
|
{
|
||||||
|
defaultWorkspaceId: userWorkspaces[0].workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,9 +12,9 @@ import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user
|
|||||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { UserModule } from 'src/engine/core-modules/user/user.module';
|
|
||||||
import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener';
|
import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener';
|
||||||
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
||||||
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
|
||||||
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
||||||
import { Workspace } from './workspace.entity';
|
import { Workspace } from './workspace.entity';
|
||||||
@ -30,14 +30,13 @@ import { WorkspaceService } from './services/workspace.service';
|
|||||||
FileUploadModule,
|
FileUploadModule,
|
||||||
WorkspaceCacheVersionModule,
|
WorkspaceCacheVersionModule,
|
||||||
NestjsQueryTypeOrmModule.forFeature(
|
NestjsQueryTypeOrmModule.forFeature(
|
||||||
[Workspace, UserWorkspace, FeatureFlagEntity],
|
[User, Workspace, UserWorkspace, FeatureFlagEntity],
|
||||||
'core',
|
'core',
|
||||||
),
|
),
|
||||||
UserWorkspaceModule,
|
UserWorkspaceModule,
|
||||||
WorkspaceManagerModule,
|
WorkspaceManagerModule,
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
UserModule,
|
|
||||||
],
|
],
|
||||||
services: [WorkspaceService],
|
services: [WorkspaceService],
|
||||||
resolvers: workspaceAutoResolverOpts,
|
resolvers: workspaceAutoResolverOpts,
|
||||||
|
|||||||
@ -16,6 +16,12 @@ import { EmailModule } from 'src/engine/integrations/email/email.module';
|
|||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
import { CleanInactiveWorkspaceJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-inactive-workspace.job';
|
import { CleanInactiveWorkspaceJob } from 'src/engine/workspace-manager/workspace-cleaner/crons/clean-inactive-workspace.job';
|
||||||
|
import { CalendarEventParticipantModule } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.module';
|
||||||
|
import { GmailFetchMessageContentFromCacheModule } from 'src/modules/messaging/services/gmail-fetch-message-content-from-cache/gmail-fetch-message-content-from-cache.module';
|
||||||
|
import { GmailFullSyncModule } from 'src/modules/messaging/services/gmail-full-sync/gmail-full-sync.module';
|
||||||
|
import { GmailPartialSyncModule } from 'src/modules/messaging/services/gmail-partial-sync/gmail-partial-sync.module';
|
||||||
|
import { TimelineActivityModule } from 'src/modules/timeline/timeline-activity.module';
|
||||||
|
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
||||||
import { CalendarMessagingParticipantJobModule } from 'src/modules/calendar-messaging-participant/jobs/calendar-messaging-participant-job.module';
|
import { CalendarMessagingParticipantJobModule } from 'src/modules/calendar-messaging-participant/jobs/calendar-messaging-participant-job.module';
|
||||||
import { CalendarCronJobModule } from 'src/modules/calendar/crons/jobs/calendar-cron-job.module';
|
import { CalendarCronJobModule } from 'src/modules/calendar/crons/jobs/calendar-cron-job.module';
|
||||||
import { CalendarJobModule } from 'src/modules/calendar/jobs/calendar-job.module';
|
import { CalendarJobModule } from 'src/modules/calendar/jobs/calendar-job.module';
|
||||||
@ -34,6 +40,12 @@ import { TimelineJobModule } from 'src/modules/timeline/jobs/timeline-job.module
|
|||||||
DataSeedDemoWorkspaceModule,
|
DataSeedDemoWorkspaceModule,
|
||||||
BillingModule,
|
BillingModule,
|
||||||
UserWorkspaceModule,
|
UserWorkspaceModule,
|
||||||
|
WorkspaceModule,
|
||||||
|
GmailFullSyncModule,
|
||||||
|
GmailFetchMessageContentFromCacheModule,
|
||||||
|
GmailPartialSyncModule,
|
||||||
|
CalendarEventParticipantModule,
|
||||||
|
TimelineActivityModule,
|
||||||
StripeModule,
|
StripeModule,
|
||||||
// JobsModules
|
// JobsModules
|
||||||
WorkspaceQueryRunnerJobModule,
|
WorkspaceQueryRunnerJobModule,
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export class DeleteIncompleteWorkspacesCommand extends CommandRunner {
|
|||||||
} name: '${incompleteWorkspace.displayName}'`,
|
} name: '${incompleteWorkspace.displayName}'`,
|
||||||
);
|
);
|
||||||
if (!options.dryRun) {
|
if (!options.dryRun) {
|
||||||
await this.workspaceService.solfDeleteWorkspace(incompleteWorkspace.id);
|
await this.workspaceService.softDeleteWorkspace(incompleteWorkspace.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user