Files
twenty/packages/twenty-server/src/database/commands/upgrade-version-command/1-11/1-11-clean-orphaned-role-targets.command.ts
T
2025-12-02 14:39:54 +01:00

94 lines
3.2 KiB
TypeScript

import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { In, type Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Command({
name: 'upgrade:1-11:clean-orphaned-role-targets',
description:
'Clean up roleTargets records that reference non-existent userWorkspaces',
})
export class CleanOrphanedRoleTargetsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
private hasRunOnce = false;
constructor(
@InjectRepository(RoleTargetEntity)
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
}
override async runOnWorkspace({
options,
}: RunOnWorkspaceArgs): Promise<void> {
if (this.hasRunOnce) {
this.logger.log('This command has already been run');
return;
}
const isDryRun = options.dryRun || false;
if (isDryRun) {
this.logger.log('Dry run mode: No changes will be applied');
}
const orphanedRoleTargets = await this.roleTargetRepository
.createQueryBuilder('roleTarget')
.leftJoin(
UserWorkspaceEntity,
'userWorkspace',
'userWorkspace.id = roleTarget.userWorkspaceId',
)
.where('roleTarget.userWorkspaceId IS NOT NULL')
.andWhere('userWorkspace.id IS NULL')
.select([
'roleTarget.id',
'roleTarget.userWorkspaceId',
'roleTarget.workspaceId',
])
.getMany();
if (orphanedRoleTargets.length === 0) {
this.logger.log('No orphaned roleTargets found');
return;
}
if (isDryRun) {
this.logger.log(
`DRY RUN: Would delete ${orphanedRoleTargets.length} orphaned roleTarget(s):`,
);
orphanedRoleTargets.forEach((roleTarget) => {
this.logger.log(
` - roleTargetId: ${roleTarget.id}, userWorkspaceId: ${roleTarget.userWorkspaceId}, workspaceId: ${roleTarget.workspaceId}`,
);
});
this.hasRunOnce = true;
return;
}
const orphanedIds = orphanedRoleTargets.map((roleTarget) => roleTarget.id);
await this.roleTargetRepository.delete({ id: In(orphanedIds) });
this.logger.log(
`Deleted ${orphanedRoleTargets.length} orphaned roleTarget(s)`,
);
this.hasRunOnce = true;
}
}