Move defaultAvatarUrl on userWorkspace + migration command (#12100)

closes https://github.com/twentyhq/core-team-issues/issues/883
This commit is contained in:
Etienne
2025-05-21 12:07:02 +02:00
committed by GitHub
parent 8e2d0139ed
commit 3702fefc89
13 changed files with 500 additions and 78 deletions

View File

@ -1,3 +1,4 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { FileUploadResolver } from 'src/engine/core-modules/file/file-upload/resolvers/file-upload.resolver';
@ -5,7 +6,7 @@ import { FileUploadService } from 'src/engine/core-modules/file/file-upload/serv
import { FileModule } from 'src/engine/core-modules/file/file.module';
@Module({
imports: [FileModule],
imports: [FileModule, HttpModule],
providers: [FileUploadService, FileUploadResolver],
exports: [FileUploadService, FileUploadResolver],
})

View File

@ -1,22 +1,25 @@
import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import DOMPurify from 'dompurify';
import FileType from 'file-type';
import { JSDOM } from 'jsdom';
import sharp from 'sharp';
import { v4 as uuidV4 } from 'uuid';
import { v4 as uuidV4, v4 } from 'uuid';
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
import { settings } from 'src/engine/constants/settings';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { getCropSize } from 'src/utils/image';
import { getCropSize, getImageBufferFromUrl } from 'src/utils/image';
@Injectable()
export class FileUploadService {
constructor(
private readonly fileStorage: FileStorageService,
private readonly fileService: FileService,
private readonly httpService: HttpService,
) {}
private async _uploadFile({
@ -93,6 +96,31 @@ export class FileUploadService {
};
}
async uploadImageFromUrl({
imageUrl,
fileFolder,
workspaceId,
}: {
imageUrl: string;
fileFolder: FileFolder;
workspaceId: string;
}) {
const buffer = await getImageBufferFromUrl(
imageUrl,
this.httpService.axiosRef,
);
const type = await FileType.fromBuffer(buffer);
return await this.uploadImage({
file: buffer,
filename: `${v4()}.${type?.ext}`,
mimeType: type?.mime,
fileFolder,
workspaceId,
});
}
async uploadImage({
file,
filename,

View File

@ -6,8 +6,13 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
import { FileService } from './file.service';
jest.mock('uuid', () => ({
v4: jest.fn(() => 'mocked-uuid'),
}));
describe('FileService', () => {
let service: FileService;
let fileStorageService: FileStorageService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
@ -15,7 +20,9 @@ describe('FileService', () => {
FileService,
{
provide: FileStorageService,
useValue: {},
useValue: {
copy: jest.fn(),
},
},
{
provide: TwentyConfigService,
@ -29,9 +36,35 @@ describe('FileService', () => {
}).compile();
service = module.get<FileService>(FileService);
fileStorageService = module.get<FileStorageService>(FileStorageService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('copyFileFromWorkspaceToWorkspace - should copy a file to a new workspace', async () => {
const result = await service.copyFileFromWorkspaceToWorkspace(
'workspaceId',
'path/to/file',
'newWorkspaceId',
);
expect(fileStorageService.copy).toHaveBeenCalledWith({
from: {
folderPath: 'workspace-workspaceId/path/to',
filename: 'file',
},
to: {
folderPath: 'workspace-newWorkspaceId/path/to',
filename: 'mocked-uuid',
},
});
expect(result).toEqual([
'workspace-newWorkspaceId',
'path/to',
'mocked-uuid',
]);
});
});

View File

@ -1,7 +1,10 @@
import { Injectable } from '@nestjs/common';
import { basename, dirname, extname } from 'path';
import { Stream } from 'stream';
import { v4 as uuidV4 } from 'uuid';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@ -74,4 +77,30 @@ export class FileService {
folderPath: workspaceFolderPath,
});
}
async copyFileFromWorkspaceToWorkspace(
fromWorkspaceId: string,
fromPath: string,
toWorkspaceId: string,
) {
const subFolder = dirname(fromPath);
const fromWorkspaceFolderPath = `workspace-${fromWorkspaceId}`;
const toWorkspaceFolderPath = `workspace-${toWorkspaceId}`;
const fromFilename = basename(fromPath);
const toFilename = uuidV4() + extname(fromFilename);
await this.fileStorageService.copy({
from: {
folderPath: `${fromWorkspaceFolderPath}/${subFolder}`,
filename: fromFilename,
},
to: {
folderPath: `${toWorkspaceFolderPath}/${subFolder}`,
filename: toFilename,
},
});
return [toWorkspaceFolderPath, subFolder, toFilename];
}
}