Files
twenty/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-identify-field-permission-metadata.command.ts
T
Paul Rastoin cd23a2bc80 Cleaning UpgradeCommand code flow (#19241)
# Introduction
Currently preparing the `UpgradeCommand` refactor, in this way started
by cleaning up the existing avoiding unecessary dependencies to others
services allowing easier readability and concern centralization for
upcoming refactor
The UpgradeCommand was extending up to five classes, overriding
abstracted class and so on. It was also cascade
injecting 3 services

Introducing the `WorkspaceIteratorService` that centralize the commands
set to run over a single workspace logic shared between both atomic
upgrade command call and global upgradeCommand

## Tradeoff
Duplicated `@Option` between both `UpgradeCommandRunner` and
`WorkspaceMigrationRunner`


## Before
```
UpgradeCommand
  └─ extends UpgradeCommandRunner
       └─ extends ActiveOrSuspendedWorkspacesMigrationCommandRunner
            └─ extends WorkspacesMigrationCommandRunner  (owns workspace iteration loop + ORM deps)
                 └─ extends MigrationCommandRunner       (dry-run, verbose, error handling)
                      └─ extends CommandRunner            (nest-commander)
```

## Now

```
UpgradeCommand
  └─ extends UpgradeCommandRunner
       └─ extends CommandRunner                 (nest-commander)
       uses ─► Services         (via composition)
```

## Logging management
At the moment all services are logging, in the best of the world only
the runners should be doing so
2026-04-02 13:24:55 +00:00

107 lines
3.3 KiB
TypeScript

import { InjectDataSource } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { DataSource, IsNull } from 'typeorm';
import { v4 } from 'uuid';
import { isDefined } from 'twenty-shared/utils';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
@Command({
name: 'upgrade:1-20:identify-field-permission-metadata',
description:
'Identify field permission metadata (backfill universalIdentifier and applicationId)',
})
export class IdentifyFieldPermissionMetadataCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
private hasRunOnce = false;
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
options,
}: RunOnWorkspaceArgs): Promise<void> {
if (this.hasRunOnce) {
this.logger.log(
'Skipping has already been run once IdentifyFieldPermissionMetadataCommand',
);
return;
}
if (options.dryRun) {
return;
}
const queryRunner = this.coreDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const repository = queryRunner.manager.getRepository(
FieldPermissionEntity,
);
const withNullApplicationId = await repository.find({
where: { applicationId: IsNull() },
relations: ['role'],
});
const toUpdate = withNullApplicationId.filter((fieldPermission) =>
isDefined(fieldPermission.role?.applicationId),
);
const toRemove = withNullApplicationId.filter(
(fieldPermission) => !isDefined(fieldPermission.role?.applicationId),
);
for (const fieldPermission of toUpdate) {
fieldPermission.applicationId = fieldPermission.role!.applicationId;
fieldPermission.universalIdentifier =
fieldPermission.universalIdentifier ?? v4();
}
if (toUpdate.length > 0) {
await repository.save(toUpdate);
}
if (toRemove.length > 0) {
await repository.remove(toRemove);
}
const withNullUniversalIdentifier = await repository.find({
where: { universalIdentifier: IsNull() },
});
for (const fieldPermission of withNullUniversalIdentifier) {
fieldPermission.universalIdentifier = v4();
}
if (withNullUniversalIdentifier.length > 0) {
await repository.save(withNullUniversalIdentifier);
}
await queryRunner.commitTransaction();
this.logger.log(
'Successfully run IdentifyFieldPermissionMetadataCommand',
);
this.hasRunOnce = true;
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error(
`Rolling back IdentifyFieldPermissionMetadataCommand: ${error.message}`,
);
throw error;
} finally {
await queryRunner.release();
}
}
}