File v2 - update core.file table (#17172)

For the following, I'll create the issues first
This commit is contained in:
Etienne
2026-01-16 16:59:39 +01:00
committed by GitHub
parent dcdf9e4d9c
commit 76cbe59929
16 changed files with 229 additions and 65 deletions
@@ -8,17 +8,11 @@ export class FileDTO {
id: string;
@Field()
name: string;
@Field()
fullPath: string;
path: string;
@Field()
size: number;
@Field()
type: string;
@Field(() => Date, { nullable: false })
createdAt: Date;
}
@@ -3,11 +3,17 @@ import { ObjectType } from '@nestjs/graphql';
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity';
@Entity('file')
@@ -17,18 +23,30 @@ export class FileEntity extends WorkspaceRelatedEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: false })
name: string;
@Column({ nullable: true, type: 'uuid' })
applicationId: string;
@ManyToOne('ApplicationEntity', {
onDelete: 'RESTRICT',
})
@JoinColumn({ name: 'applicationId' })
application: Relation<ApplicationEntity>;
@Column({ nullable: false })
fullPath: string;
path: string;
@Column({ nullable: false, type: 'bigint' })
size: number;
@Column({ nullable: false })
type: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@DeleteDateColumn({ type: 'timestamptz' })
deletedAt: Date | null;
@Column({ nullable: false, default: false })
isStaticAsset: boolean;
}
@@ -1,7 +1,7 @@
import { UnrecoverableError } from 'bullmq';
import { extractFolderPathFilenameAndTypeOrThrow } from 'twenty-shared/utils';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { extractFolderPathAndFilename } from 'src/engine/core-modules/file/utils/extract-folderpath-and-filename.utils';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
@@ -19,7 +19,8 @@ export class FileDeletionJob {
async handle(data: FileDeletionJobData): Promise<void> {
const { workspaceId, fullPath } = data;
const { folderPath, filename } = extractFolderPathAndFilename(fullPath);
const { folderPath, filename } =
extractFolderPathFilenameAndTypeOrThrow(fullPath);
if (!filename) {
throw new UnrecoverableError(
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { extractFolderPathFilenameAndTypeOrThrow } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
@@ -8,7 +9,6 @@ import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.
import { type FileDTO } from 'src/engine/core-modules/file/dtos/file.dto';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
import { extractFolderPathAndFilename } from 'src/engine/core-modules/file/utils/extract-folderpath-and-filename.utils';
import { FileService } from './file.service';
@@ -45,10 +45,8 @@ export class FileMetadataService {
}
const createdFile = this.fileRepository.create({
name: filename,
fullPath: files[0].path,
path: files[0].path,
size: file.length,
type: mimeType,
workspaceId,
});
@@ -69,12 +67,12 @@ export class FileMetadataService {
return null;
}
const { folderPath, filename } = extractFolderPathAndFilename(
file.fullPath,
const { folderPath, filename } = extractFolderPathFilenameAndTypeOrThrow(
file.path,
);
try {
if (file.fullPath) {
if (file.path) {
await this.fileService.deleteFile({
folderPath,
filename,
@@ -4,7 +4,10 @@ import { basename, dirname, extname } from 'path';
import { type Readable } from 'stream';
import { isNonEmptyString } from '@sniptt/guards';
import { buildSignedPath } from 'twenty-shared/utils';
import {
buildSignedPath,
extractFolderPathFilenameAndTypeOrThrow,
} from 'twenty-shared/utils';
import { v4 as uuidV4 } from 'uuid';
import {
@@ -12,7 +15,6 @@ import {
JwtTokenTypeEnum,
} from 'src/engine/core-modules/auth/types/auth-context.type';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { extractFolderPathAndFilename } from 'src/engine/core-modules/file/utils/extract-folderpath-and-filename.utils';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -45,7 +47,7 @@ export class FileService {
return buildSignedPath({
path: url,
token: this.encodeFileToken({
filename: extractFolderPathAndFilename(url).filename,
filename: extractFolderPathFilenameAndTypeOrThrow(url).filename,
workspaceId,
}),
});
@@ -1,13 +0,0 @@
export function extractFolderPathAndFilename(fullPath: string): {
folderPath: string;
filename: string;
} {
if (!fullPath || typeof fullPath !== 'string') {
throw new Error('Invalid fullPath provided');
}
const parts = fullPath.split('/');
const filename = parts.pop() || '';
const folderPath = parts.join('/');
return { folderPath, filename };
}
@@ -4,13 +4,16 @@ import { InjectRepository } from '@nestjs/typeorm';
import { render, toPlainText } from '@react-email/render';
import DOMPurify from 'dompurify';
import { reactMarkupFromJSON } from 'twenty-emails';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import {
extractFolderPathFilenameAndTypeOrThrow,
isDefined,
isValidUuid,
} from 'twenty-shared/utils';
import { In, type Repository } from 'typeorm';
import { z } from 'zod';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { extractFolderPathAndFilename } from 'src/engine/core-modules/file/utils/extract-folderpath-and-filename.utils';
import {
SendEmailToolException,
SendEmailToolExceptionCode,
@@ -154,8 +157,8 @@ export class SendEmailTool implements Tool {
for (const fileMetadata of files) {
const fileEntity = fileEntityMap.get(fileMetadata.id)!;
const { folderPath, filename } = extractFolderPathAndFilename(
fileEntity.fullPath,
const { folderPath, filename } = extractFolderPathFilenameAndTypeOrThrow(
fileEntity.path,
);
const stream = await this.fileService.getFileStream(