Files
twenty/packages/twenty-server/src/database/commands/upgrade-version-command/0-54/0-54-clean-not-found-files.command.ts
Marie a189f15313 [permissions] fix workflows + remove shouldBypassPermissionChecks for system objects (#12559)
In this PR 

1. fix workflow step creation by adding forgotten
`shouldBypassPermissionChecks` in WorkflowVersionStepWorkspaceService
2. clarify the rule for twentyORMGlobalManager: do not add unnecessary
`shouldBypassPermissionChecks` for system objects (there are no
object-records permission checks on system objects, they are dealt with
at resolver level)
2025-06-12 13:56:41 +02:00

226 lines
7.0 KiB
TypeScript

import { InjectRepository } from '@nestjs/typeorm';
import { isNonEmptyString } from '@sniptt/guards';
import { Command } from 'nest-commander';
import { Equal, Not, Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Command({
name: 'upgrade:0-54:clean-not-found-files',
description: 'Clean not found files',
})
export class CleanNotFoundFilesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly fileStorageService: FileStorageService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
);
await this.cleanNotFoundFiles(workspaceId, !!options.dryRun);
}
private async cleanNotFoundFiles(workspaceId: string, dryRun: boolean) {
await this.cleanWorkspaceLogo(workspaceId, dryRun);
await this.softDeleteAttachments(workspaceId, dryRun);
await this.cleanWorkspaceMembersAvatarUrl(workspaceId, dryRun);
await this.cleanPeopleAvatarUrl(workspaceId, dryRun);
}
private async checkIfFileIsFound(path: string, workspaceId: string) {
this.logger.log(`Checking if file is found ${path}`);
if (path.startsWith('https://')) return true; // seed data
const isFileFound = await this.fileStorageService.checkFileExists({
folderPath: `workspace-${workspaceId}`,
filename: path,
});
this.logger.log(`File found: ${isFileFound}`);
return isFileFound;
}
private async cleanWorkspaceLogo(workspaceId: string, dryRun: boolean) {
this.logger.log(`Cleaning workspace logo for workspace ${workspaceId}`);
const workspace = await this.workspaceRepository.findOneOrFail({
where: {
id: workspaceId,
},
});
if (!isNonEmptyString(workspace.logo)) return;
this.logger.log(`Processing workspace logo for workspace ${workspace.id}`);
const isFileFound = await this.checkIfFileIsFound(
workspace.logo,
workspace.id,
);
if (isFileFound) return;
if (!dryRun)
await this.workspaceRepository.update(workspace.id, {
logo: '',
});
this.logger.log(
`${dryRun ? 'Dry run - ' : ''}Set logo to '' for workspace ${workspace.id}`,
);
}
private async softDeleteAttachments(workspaceId: string, dryRun: boolean) {
this.logger.log(`Cleaning attachments for workspace ${workspaceId}`);
const attachmentRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<AttachmentWorkspaceEntity>(
workspaceId,
'attachment',
);
const attachmentsCount = await attachmentRepository.count();
const chunkSize = 10;
const attachmentIdsToSoftDelete: string[] = [];
for (let offset = 0; offset < attachmentsCount; offset += chunkSize) {
const attachmentsChunk = await attachmentRepository.find({
skip: offset,
take: chunkSize,
});
const attachmentIdsToSoftDeleteChunk = await Promise.all(
attachmentsChunk.map(async (attachment) => {
this.logger.log(`Processing attachment ${attachment.id}`);
const isFileFound = await this.checkIfFileIsFound(
attachment.fullPath,
workspaceId,
);
return isFileFound ? '' : attachment.id;
}),
);
attachmentIdsToSoftDelete.push(
...attachmentIdsToSoftDeleteChunk.filter(isNonEmptyString),
);
}
if (attachmentIdsToSoftDelete.length === 0) return;
if (!dryRun)
await attachmentRepository.softDelete(attachmentIdsToSoftDelete);
this.logger.log(
`${dryRun ? 'Dry run - ' : ''}Deleted attachments ${attachmentIdsToSoftDelete.join(', ')}`,
);
}
private async cleanWorkspaceMembersAvatarUrl(
workspaceId: string,
dryRun: boolean,
) {
this.logger.log(
`Cleaning workspace members avatarUrl for workspace ${workspaceId}`,
);
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const workspaceMembers = await workspaceMemberRepository.find({
where: {
avatarUrl: Not(Equal('')),
},
});
const workspaceMemberIdsToUpdate: string[] = [];
for (const workspaceMember of workspaceMembers) {
this.logger.log(`Processing workspaceMember ${workspaceMember.id}`);
const isFileFound = await this.checkIfFileIsFound(
workspaceMember.avatarUrl,
workspaceId,
);
if (isFileFound) continue;
workspaceMemberIdsToUpdate.push(workspaceMember.id);
}
if (workspaceMemberIdsToUpdate.length === 0) return;
if (!dryRun)
await workspaceMemberRepository.update(workspaceMemberIdsToUpdate, {
avatarUrl: '',
});
this.logger.log(
`${dryRun ? 'Dry run - ' : ''}Set avatarUrl to '' for workspaceMembers ${workspaceMemberIdsToUpdate.join(', ')}`,
);
}
private async cleanPeopleAvatarUrl(workspaceId: string, dryRun: boolean) {
this.logger.log(`Cleaning people avatarUrl for workspace ${workspaceId}`);
const personRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<PersonWorkspaceEntity>(
workspaceId,
'person',
{ shouldBypassPermissionChecks: true },
);
const people = await personRepository.find({
where: {
avatarUrl: Not(Equal('')),
},
});
const personIdsToUpdate: string[] = [];
for (const person of people) {
this.logger.log('Processing person', person.id);
const isFileFound = await this.checkIfFileIsFound(
person.avatarUrl,
workspaceId,
);
if (!isFileFound) {
personIdsToUpdate.push(person.id);
}
}
if (personIdsToUpdate.length === 0) return;
if (!dryRun)
await personRepository.update(personIdsToUpdate, {
avatarUrl: '',
});
this.logger.log(
`${dryRun ? 'Dry run - ' : ''}Set avatarUrl to '' for people ${personIdsToUpdate.join(', ')}`,
);
}
}