add checkFileExists method in file storage service (#12229)

This commit is contained in:
Etienne
2025-05-22 17:09:21 +02:00
committed by GitHub
parent 08e32017fb
commit 7cc0a7ae72
6 changed files with 57 additions and 27 deletions

View File

@ -1,21 +1,14 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { basename, dirname } from 'path';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { Command } from 'nest-commander'; import { Command } from 'nest-commander';
import { Equal, Not, Repository } from 'typeorm'; import { Equal, Not, Repository } from 'typeorm';
import {
FileStorageException,
FileStorageExceptionCode,
} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
import { import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner, ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs, RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
@ -31,7 +24,7 @@ export class CleanNotFoundFilesCommand extends ActiveOrSuspendedWorkspacesMigrat
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>, protected readonly workspaceRepository: Repository<Workspace>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly fileService: FileService, private readonly fileStorageService: FileStorageService,
) { ) {
super(workspaceRepository, twentyORMGlobalManager); super(workspaceRepository, twentyORMGlobalManager);
} }
@ -60,25 +53,14 @@ export class CleanNotFoundFilesCommand extends ActiveOrSuspendedWorkspacesMigrat
this.logger.log(`Checking if file is found ${path}`); this.logger.log(`Checking if file is found ${path}`);
if (path.startsWith('https://')) return true; // seed data if (path.startsWith('https://')) return true; // seed data
try { const isFileFound = await this.fileStorageService.checkFileExists({
await this.fileService.getFileStream( folderPath: `workspace-${workspaceId}`,
dirname(path), filename: path,
basename(path), });
workspaceId,
);
} catch (error) {
if (
error instanceof FileStorageException &&
error.code === FileStorageExceptionCode.FILE_NOT_FOUND
) {
this.logger.log(`File not found`);
return false; this.logger.log(`File found: ${isFileFound}`);
}
}
this.logger.log(`File found`);
return true; return isFileFound;
} }
private async cleanWorkspaceLogo(workspaceId: string, dryRun: boolean) { private async cleanWorkspaceLogo(workspaceId: string, dryRun: boolean) {

View File

@ -7,6 +7,7 @@ import { FixStandardSelectFieldsPositionCommand } from 'src/database/commands/up
import { LowercaseUserAndInvitationEmailsCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command'; import { LowercaseUserAndInvitationEmailsCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command';
import { MigrateDefaultAvatarUrlToUserWorkspaceCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command'; import { MigrateDefaultAvatarUrlToUserWorkspaceCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command';
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
import { FileModule } from 'src/engine/core-modules/file/file.module'; import { FileModule } from 'src/engine/core-modules/file/file.module';
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 { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
@ -31,6 +32,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor
WorkspaceMigrationRunnerModule, WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule, WorkspaceMetadataVersionModule,
FileModule, FileModule,
FileStorageModule,
], ],
providers: [ providers: [
FixStandardSelectFieldsPositionCommand, FixStandardSelectFieldsPositionCommand,

View File

@ -21,4 +21,8 @@ export interface StorageDriver {
from: { folderPath: string; filename?: string }; from: { folderPath: string; filename?: string };
to: { folderPath: string; filename?: string }; to: { folderPath: string; filename?: string };
}): Promise<void>; }): Promise<void>;
checkFileExists(params: {
folderPath: string;
filename: string;
}): Promise<boolean>;
} }

View File

@ -3,11 +3,11 @@ import * as fs from 'fs/promises';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { StorageDriver } from 'src/engine/core-modules/file-storage/drivers/interfaces/storage-driver.interface';
import { import {
FileStorageException, FileStorageException,
FileStorageExceptionCode, FileStorageExceptionCode,
} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
import { StorageDriver } from 'src/engine/core-modules/file-storage/drivers/interfaces/storage-driver.interface';
export interface LocalDriverOptions { export interface LocalDriverOptions {
storagePath: string; storagePath: string;
@ -162,4 +162,17 @@ export class LocalDriver implements StorageDriver {
}): Promise<void> { }): Promise<void> {
await this.copy(params, true); await this.copy(params, true);
} }
async checkFileExists(params: {
folderPath: string;
filename: string;
}): Promise<boolean> {
const filePath = join(
this.options.storagePath,
params.folderPath,
params.filename,
);
return existsSync(filePath);
}
} }

View File

@ -395,4 +395,26 @@ export class S3Driver implements StorageDriver {
return this.s3Client.createBucket(args); return this.s3Client.createBucket(args);
} }
async checkFileExists(params: {
folderPath: string;
filename: string;
}): Promise<boolean> {
try {
await this.s3Client.send(
new HeadObjectCommand({
Bucket: this.bucketName,
Key: `${params.folderPath}/${params.filename}`,
}),
);
} catch (error) {
if (error instanceof NotFound) {
return false;
}
throw error;
}
return true;
}
} }

View File

@ -47,4 +47,11 @@ export class FileStorageService implements StorageDriver {
}): Promise<void> { }): Promise<void> {
return this.driver.download(params); return this.driver.download(params);
} }
checkFileExists(params: {
folderPath: string;
filename: string;
}): Promise<boolean> {
return this.driver.checkFileExists(params);
}
} }