File storage refactor - Switch to applicationUniversalIdentifier (#17541)

This commit is contained in:
Etienne
2026-01-29 11:39:19 +01:00
committed by GitHub
parent 3188fb5295
commit 22c7e207e3
6 changed files with 54 additions and 11 deletions
@@ -3,6 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { Readable } from 'stream';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FileStorageDriverFactory } from 'src/engine/core-modules/file-storage/file-storage-driver.factory';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
@@ -19,6 +20,10 @@ describe('FileStorageService', () => {
save: jest.fn(),
};
const mockApplicationRepository = {
findOneOrFail: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
@@ -31,6 +36,10 @@ describe('FileStorageService', () => {
provide: getRepositoryToken(FileEntity),
useValue: mockFileRepository,
},
{
provide: getRepositoryToken(ApplicationEntity),
useValue: mockApplicationRepository,
},
],
}).compile();
@@ -1,6 +1,7 @@
import { type DynamicModule, Global } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FileStorageDriverFactory } from 'src/engine/core-modules/file-storage/file-storage-driver.factory';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
@@ -11,7 +12,10 @@ export class FileStorageModule {
static forRoot(): DynamicModule {
return {
module: FileStorageModule,
imports: [TwentyConfigModule, TypeOrmModule.forFeature([FileEntity])],
imports: [
TwentyConfigModule,
TypeOrmModule.forFeature([FileEntity, ApplicationEntity]),
],
providers: [FileStorageDriverFactory, FileStorageService],
exports: [FileStorageService],
};
@@ -6,6 +6,7 @@ import { type Readable } from 'stream';
import { FileFolder, Sources } from 'twenty-shared/types';
import { Repository } from 'typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FileStorageDriverFactory } from 'src/engine/core-modules/file-storage/file-storage-driver.factory';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
@@ -17,6 +18,8 @@ export class FileStorageService {
private readonly fileStorageDriverFactory: FileStorageDriverFactory,
@InjectRepository(FileEntity)
private readonly fileRepository: Repository<FileEntity>,
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
) {}
/**
@@ -44,7 +47,7 @@ export class FileStorageService {
destinationPath,
mimeType,
fileFolder,
applicationId,
applicationUniversalIdentifier,
workspaceId,
fileId,
}: {
@@ -52,14 +55,21 @@ export class FileStorageService {
destinationPath: string;
mimeType: string | undefined;
fileFolder: FileFolder;
applicationId: string;
applicationUniversalIdentifier: string;
workspaceId: string;
fileId?: string;
}): Promise<FileEntity> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
const application = await this.applicationRepository.findOneOrFail({
where: {
universalIdentifier: applicationUniversalIdentifier,
workspaceId,
},
});
const driverParams = {
filePath: `${workspaceId}/${applicationId}/${fileFolder}/${destinationPath}`,
filePath: `${workspaceId}/${applicationUniversalIdentifier}/${fileFolder}/${destinationPath}`,
mimeType,
sourceFile,
};
@@ -69,7 +79,7 @@ export class FileStorageService {
const fileEntity = await this.fileRepository.save({
path: `${fileFolder}/${destinationPath}`,
workspaceId,
applicationId,
applicationId: application.id,
id: fileId,
size:
typeof sourceFile === 'string'
@@ -93,17 +103,17 @@ export class FileStorageService {
read_v2({
destinationPath,
fileFolder,
applicationId,
applicationUniversalIdentifier,
workspaceId,
}: {
destinationPath: string;
fileFolder: FileFolder;
applicationId: string;
applicationUniversalIdentifier: string;
workspaceId: string;
}): Promise<Readable> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
const folderPath = `${workspaceId}/${applicationId}/${fileFolder}/${destinationPath}`;
const folderPath = `${workspaceId}/${applicationUniversalIdentifier}/${fileFolder}/${destinationPath}`;
return driver.read({ filePath: folderPath });
}
@@ -1,13 +1,20 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FileUploadResolver } from 'src/engine/core-modules/file/file-upload/resolvers/file-upload.resolver';
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@Module({
imports: [FileModule, HttpModule, PermissionsModule],
imports: [
FileModule,
HttpModule,
PermissionsModule,
TypeOrmModule.forFeature([ApplicationEntity]),
],
providers: [FileUploadService, FileUploadResolver],
exports: [FileUploadService, FileUploadResolver],
})
@@ -1,13 +1,16 @@
import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import DOMPurify from 'dompurify';
import FileType from 'file-type';
import sharp from 'sharp';
import { FileFolder } from 'twenty-shared/types';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { settings } from 'src/engine/constants/settings';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
@@ -29,6 +32,8 @@ export class FileUploadService {
private readonly fileStorage: FileStorageService,
private readonly fileService: FileService,
private readonly httpService: HttpService,
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
) {}
private async _uploadFile({
@@ -236,12 +241,19 @@ export class FileUploadService {
const fileId = v4();
const name = `${fileId}${ext ? `.${ext}` : ''}`;
const application = await this.applicationRepository.findOneOrFail({
where: {
id: applicationId,
workspaceId,
},
});
return await this.fileStorage.write_v2({
sourceFile: sanitizedFile,
destinationPath: name,
mimeType,
fileFolder: FileFolder.FilesField,
applicationId,
applicationUniversalIdentifier: application.universalIdentifier,
workspaceId,
fileId,
});
@@ -2,6 +2,7 @@ import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FilePathGuard } from 'src/engine/core-modules/file/guards/file-path-guard';
import { FileDeletionJob } from 'src/engine/core-modules/file/jobs/file-deletion.job';
import { FileWorkspaceFolderDeletionJob } from 'src/engine/core-modules/file/jobs/file-workspace-folder-deletion.job';
@@ -21,7 +22,7 @@ import { FileService } from './services/file.service';
@Module({
imports: [
JwtModule,
TypeOrmModule.forFeature([FileEntity, WorkspaceEntity]),
TypeOrmModule.forFeature([FileEntity, WorkspaceEntity, ApplicationEntity]),
HttpModule,
PermissionsModule,
],