@@ -1,4 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
@@ -20,9 +21,12 @@ import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([FileEntity]),
|
||||
ApplicationModule,
|
||||
ApplicationVariableEntityModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
@@ -40,6 +44,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
ObjectPermissionModule,
|
||||
PermissionFlagModule,
|
||||
WorkflowCommonModule,
|
||||
FileStorageModule,
|
||||
],
|
||||
providers: [
|
||||
ApplicationResolver,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { UseFilters, UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import path, { join } from 'path';
|
||||
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { type Repository } from 'typeorm';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import type { FileUpload } from 'graphql-upload/processRequest.mjs';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
||||
@@ -11,12 +19,21 @@ import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/applica
|
||||
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
|
||||
import { UninstallApplicationInput } from 'src/engine/core-modules/application/dtos/uninstallApplicationInput';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { UploadApplicationFileInput } from 'src/engine/core-modules/application/dtos/uploadApplicationFileInput';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { FileDTO } from 'src/engine/core-modules/file/dtos/file.dto';
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
@@ -29,6 +46,9 @@ export class ApplicationResolver {
|
||||
constructor(
|
||||
private readonly applicationSyncService: ApplicationSyncService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
@InjectRepository(FileEntity)
|
||||
private readonly fileRepository: Repository<FileEntity>,
|
||||
) {}
|
||||
|
||||
@Query(() => [ApplicationDTO])
|
||||
@@ -75,4 +95,61 @@ export class ApplicationResolver {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => FileDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.UPLOAD_FILE))
|
||||
async uploadApplicationFile(
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, mimetype }: FileUpload,
|
||||
@Args()
|
||||
{
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
filePath,
|
||||
}: UploadApplicationFileInput,
|
||||
): Promise<FileDTO> {
|
||||
const allowedApplicationFileFolders: FileFolder[] = [
|
||||
FileFolder.Functions,
|
||||
FileFolder.FrontComponents,
|
||||
FileFolder.Assets,
|
||||
FileFolder.SourceCode,
|
||||
];
|
||||
|
||||
if (!allowedApplicationFileFolders.includes(fileFolder)) {
|
||||
throw new ApplicationException(
|
||||
`Invalid fileFolder for application file upload. Allowed values: ${allowedApplicationFileFolders.join(', ')}`,
|
||||
ApplicationExceptionCode.INVALID_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
|
||||
const dirname = path.dirname(filePath);
|
||||
|
||||
const filename = path.basename(filePath);
|
||||
|
||||
const folderPath = join(
|
||||
`workspace-${workspaceId}`,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
dirname,
|
||||
);
|
||||
|
||||
await this.fileStorageService.write({
|
||||
file: buffer,
|
||||
name: filename,
|
||||
folder: folderPath,
|
||||
mimeType: mimetype,
|
||||
});
|
||||
|
||||
const createdFile = this.fileRepository.create({
|
||||
path: join(folderPath, filename),
|
||||
size: buffer.length,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return await this.fileRepository.save(createdFile);
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
@ArgsType()
|
||||
export class UploadApplicationFileInput {
|
||||
@Field(() => String)
|
||||
applicationUniversalIdentifier: string;
|
||||
|
||||
@Field(() => FileFolder)
|
||||
fileFolder: FileFolder;
|
||||
|
||||
@Field(() => String)
|
||||
filePath: string;
|
||||
}
|
||||
+1
-2
@@ -3,8 +3,7 @@ import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import type { FileUpload } from 'graphql-upload/processRequest.mjs';
|
||||
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@ import DOMPurify from 'dompurify';
|
||||
import FileType from 'file-type';
|
||||
import sharp from 'sharp';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { type FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { settings } from 'src/engine/constants/settings';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
|
||||
+13
-11
@@ -1,17 +1,7 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { type KebabCase } from 'type-fest';
|
||||
|
||||
export enum FileFolder {
|
||||
ProfilePicture = 'profile-picture',
|
||||
WorkspaceLogo = 'workspace-logo',
|
||||
Attachment = 'attachment',
|
||||
PersonPicture = 'person-picture',
|
||||
ServerlessFunction = 'serverless-function',
|
||||
ServerlessFunctionToDelete = 'serverless-function-to-delete',
|
||||
File = 'file',
|
||||
AgentChat = 'agent-chat',
|
||||
}
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
registerEnumType(FileFolder, {
|
||||
name: 'FileFolder',
|
||||
@@ -46,6 +36,18 @@ export const fileFolderConfigs: Record<FileFolder, FileFolderConfig> = {
|
||||
[FileFolder.AgentChat]: {
|
||||
ignoreExpirationToken: false,
|
||||
},
|
||||
[FileFolder.Functions]: {
|
||||
ignoreExpirationToken: false,
|
||||
},
|
||||
[FileFolder.FrontComponents]: {
|
||||
ignoreExpirationToken: false,
|
||||
},
|
||||
[FileFolder.Assets]: {
|
||||
ignoreExpirationToken: true,
|
||||
},
|
||||
[FileFolder.SourceCode]: {
|
||||
ignoreExpirationToken: false,
|
||||
},
|
||||
};
|
||||
|
||||
export type AllowedFolders = KebabCase<keyof typeof FileFolder>;
|
||||
|
||||
+1
-2
@@ -3,8 +3,7 @@ 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';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { type FileDTO } from 'src/engine/core-modules/file/dtos/file.dto';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { checkFileFolder } from 'src/engine/core-modules/file/utils/check-file-folder.utils';
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { checkFilename } from 'src/engine/core-modules/file/utils/check-file-name.utils';
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { checkFilePath } from 'src/engine/core-modules/file/utils/check-file-path.utils';
|
||||
|
||||
|
||||
+3
-4
@@ -1,9 +1,8 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
type AllowedFolders,
|
||||
FileFolder,
|
||||
} from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { type AllowedFolders } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { kebabCase } from 'src/utils/kebab-case';
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
type AllowedFolders,
|
||||
FileFolder,
|
||||
} from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { type AllowedFolders } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { kebabCase } from 'src/utils/kebab-case';
|
||||
import { settings } from 'src/engine/constants/settings';
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import {
|
||||
|
||||
+1
-1
@@ -9,12 +9,12 @@ import {
|
||||
type CodeExecutionState,
|
||||
} from 'twenty-shared/ai';
|
||||
import { v4 } from 'uuid';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
type InputFile,
|
||||
type OutputFile,
|
||||
} from 'src/engine/core-modules/code-interpreter/drivers/interfaces/code-interpreter-driver.interface';
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import {
|
||||
type AccessTokenJwtPayload,
|
||||
|
||||
+1
-2
@@ -2,8 +2,7 @@ import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import type { FileUpload } from 'graphql-upload/processRequest.mjs';
|
||||
|
||||
|
||||
+1
-2
@@ -2,8 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { type DataSource, type Repository } from 'typeorm';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { type ApprovedAccessDomainEntity } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||
|
||||
+1
-1
@@ -4,9 +4,9 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryRunner, IsNull, Not, type Repository } from 'typeorm';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||
|
||||
@@ -19,8 +19,7 @@ import assert from 'assert';
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import type { FileUpload } from 'graphql-upload/processRequest.mjs';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user