file storage workspace id prefix (#6230)
closes https://github.com/twentyhq/twenty/issues/6155 just an idea, i guess this could work well, but im open for discussion --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@ -75,11 +75,6 @@ export class SignInUpService {
|
||||
|
||||
const passwordHash = password ? await hashPassword(password) : undefined;
|
||||
|
||||
let imagePath: string | undefined;
|
||||
|
||||
if (picture) {
|
||||
imagePath = await this.uploadPicture(picture);
|
||||
}
|
||||
const existingUser = await this.userRepository.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
@ -103,7 +98,7 @@ export class SignInUpService {
|
||||
workspaceInviteHash,
|
||||
firstName,
|
||||
lastName,
|
||||
imagePath,
|
||||
picture,
|
||||
existingUser,
|
||||
});
|
||||
}
|
||||
@ -113,7 +108,7 @@ export class SignInUpService {
|
||||
passwordHash,
|
||||
firstName,
|
||||
lastName,
|
||||
imagePath,
|
||||
picture,
|
||||
});
|
||||
}
|
||||
|
||||
@ -126,7 +121,7 @@ export class SignInUpService {
|
||||
workspaceInviteHash,
|
||||
firstName,
|
||||
lastName,
|
||||
imagePath,
|
||||
picture,
|
||||
existingUser,
|
||||
}: {
|
||||
email: string;
|
||||
@ -134,7 +129,7 @@ export class SignInUpService {
|
||||
workspaceInviteHash: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
imagePath: string | undefined;
|
||||
picture: SignInUpServiceInput['picture'];
|
||||
existingUser: User | null;
|
||||
}) {
|
||||
const workspace = await this.workspaceRepository.findOneBy({
|
||||
@ -162,6 +157,8 @@ export class SignInUpService {
|
||||
return Object.assign(existingUser, updatedUser);
|
||||
}
|
||||
|
||||
const imagePath = await this.uploadPicture(picture, workspace.id);
|
||||
|
||||
const userToCreate = this.userRepository.create({
|
||||
email: email,
|
||||
firstName: firstName,
|
||||
@ -185,13 +182,13 @@ export class SignInUpService {
|
||||
passwordHash,
|
||||
firstName,
|
||||
lastName,
|
||||
imagePath,
|
||||
picture,
|
||||
}: {
|
||||
email: string;
|
||||
passwordHash: string | undefined;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
imagePath: string | undefined;
|
||||
picture: SignInUpServiceInput['picture'];
|
||||
}) {
|
||||
assert(
|
||||
!this.environmentService.get('IS_SIGN_UP_DISABLED'),
|
||||
@ -208,6 +205,8 @@ export class SignInUpService {
|
||||
|
||||
const workspace = await this.workspaceRepository.save(workspaceToCreate);
|
||||
|
||||
const imagePath = await this.uploadPicture(picture, workspace.id);
|
||||
|
||||
const userToCreate = this.userRepository.create({
|
||||
email: email,
|
||||
firstName: firstName,
|
||||
@ -225,7 +224,14 @@ export class SignInUpService {
|
||||
return user;
|
||||
}
|
||||
|
||||
async uploadPicture(picture: string): Promise<string> {
|
||||
async uploadPicture(
|
||||
picture: string | null | undefined,
|
||||
workspaceId: string,
|
||||
): Promise<string | undefined> {
|
||||
if (!picture) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = await getImageBufferFromUrl(
|
||||
picture,
|
||||
this.httpService.axiosRef,
|
||||
@ -238,6 +244,7 @@ export class SignInUpService {
|
||||
filename: `${v4()}.${type?.ext}`,
|
||||
mimeType: type?.mime,
|
||||
fileFolder: FileFolder.ProfilePicture,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
import { Controller, Get, Param, Res, UseGuards } from '@nestjs/common';
|
||||
import { Controller, Get, Param, Req, Res, UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Response } from 'express';
|
||||
|
||||
import { FilePathGuard } from 'src/engine/core-modules/file/guards/file-path-guard';
|
||||
import {
|
||||
FileStorageException,
|
||||
FileStorageExceptionCode,
|
||||
} from 'src/engine/integrations/file-storage/interfaces/file-storage-exception';
|
||||
|
||||
import {
|
||||
checkFilePath,
|
||||
checkFilename,
|
||||
} from 'src/engine/core-modules/file/file.utils';
|
||||
import { FilePathGuard } from 'src/engine/core-modules/file/guards/file-path-guard';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
|
||||
// TODO: Add cookie authentication
|
||||
@ -15,23 +20,38 @@ import { FileService } from 'src/engine/core-modules/file/services/file.service'
|
||||
export class FileController {
|
||||
constructor(private readonly fileService: FileService) {}
|
||||
|
||||
/**
|
||||
* Serve files from local storage
|
||||
* We recommend using an s3 bucket for production
|
||||
*/
|
||||
@Get('*/:filename')
|
||||
async getFile(@Param() params: string[], @Res() res: Response) {
|
||||
async getFile(
|
||||
@Param() params: string[],
|
||||
@Res() res: Response,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
const folderPath = checkFilePath(params[0]);
|
||||
const filename = checkFilename(params['filename']);
|
||||
const fileStream = await this.fileService.getFileStream(
|
||||
folderPath,
|
||||
filename,
|
||||
);
|
||||
|
||||
fileStream.on('error', () => {
|
||||
res.status(404).send({ error: 'File not found' });
|
||||
});
|
||||
const workspaceId = (req as any)?.workspaceId;
|
||||
|
||||
fileStream.pipe(res);
|
||||
if (!workspaceId) {
|
||||
return res.status(401).send({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
const fileStream = await this.fileService.getFileStream(
|
||||
folderPath,
|
||||
filename,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
fileStream.pipe(res);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof FileStorageException &&
|
||||
error.code === FileStorageExceptionCode.FILE_NOT_FOUND
|
||||
) {
|
||||
return res.status(404).send({ error: 'File not found' });
|
||||
}
|
||||
|
||||
return res.status(500).send({ error: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { GraphQLUpload, FileUpload } from 'graphql-upload';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
|
||||
@UseGuards(JwtAuthGuard, DemoEnvGuard)
|
||||
@Resolver()
|
||||
@ -17,6 +19,7 @@ export class FileUploadResolver {
|
||||
|
||||
@Mutation(() => String)
|
||||
async uploadFile(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
@Args('fileFolder', { type: () => FileFolder, nullable: true })
|
||||
@ -30,6 +33,7 @@ export class FileUploadResolver {
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return path;
|
||||
@ -37,6 +41,7 @@ export class FileUploadResolver {
|
||||
|
||||
@Mutation(() => String)
|
||||
async uploadImage(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
@Args('fileFolder', { type: () => FileFolder, nullable: true })
|
||||
@ -50,6 +55,7 @@ export class FileUploadResolver {
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import DOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import sharp from 'sharp';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { getCropSize } from 'src/utils/image';
|
||||
import { settings } from 'src/engine/constants/settings';
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
import { getCropSize } from 'src/utils/image';
|
||||
|
||||
@Injectable()
|
||||
export class FileUploadService {
|
||||
@ -19,18 +19,18 @@ export class FileUploadService {
|
||||
file,
|
||||
filename,
|
||||
mimeType,
|
||||
fileFolder,
|
||||
folder,
|
||||
}: {
|
||||
file: Buffer | Uint8Array | string;
|
||||
filename: string;
|
||||
mimeType: string | undefined;
|
||||
fileFolder: FileFolder;
|
||||
folder: string;
|
||||
}) {
|
||||
await this.fileStorage.write({
|
||||
file,
|
||||
name: filename,
|
||||
mimeType,
|
||||
folder: fileFolder,
|
||||
folder,
|
||||
});
|
||||
}
|
||||
|
||||
@ -58,21 +58,24 @@ export class FileUploadService {
|
||||
filename,
|
||||
mimeType,
|
||||
fileFolder,
|
||||
workspaceId,
|
||||
}: {
|
||||
file: Buffer | Uint8Array | string;
|
||||
filename: string;
|
||||
mimeType: string | undefined;
|
||||
fileFolder: FileFolder;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const ext = filename.split('.')?.[1];
|
||||
const id = uuidV4();
|
||||
const name = `${id}${ext ? `.${ext}` : ''}`;
|
||||
const folder = this.getWorkspaceFolderName(workspaceId, fileFolder);
|
||||
|
||||
await this._uploadFile({
|
||||
file: this._sanitizeFile({ file, ext, mimeType }),
|
||||
filename: name,
|
||||
mimeType,
|
||||
fileFolder,
|
||||
folder,
|
||||
});
|
||||
|
||||
return {
|
||||
@ -87,11 +90,13 @@ export class FileUploadService {
|
||||
filename,
|
||||
mimeType,
|
||||
fileFolder,
|
||||
workspaceId,
|
||||
}: {
|
||||
file: Buffer | Uint8Array | string;
|
||||
filename: string;
|
||||
mimeType: string | undefined;
|
||||
fileFolder: FileFolder;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const ext = filename.split('.')?.[1];
|
||||
const id = uuidV4();
|
||||
@ -117,6 +122,7 @@ export class FileUploadService {
|
||||
await Promise.all(
|
||||
images.map(async (image, index) => {
|
||||
const buffer = await image.toBuffer();
|
||||
const folder = this.getWorkspaceFolderName(workspaceId, fileFolder);
|
||||
|
||||
paths.push(`${fileFolder}/${cropSizes[index]}/${name}`);
|
||||
|
||||
@ -124,7 +130,7 @@ export class FileUploadService {
|
||||
file: buffer,
|
||||
filename: `${cropSizes[index]}/${name}`,
|
||||
mimeType,
|
||||
fileFolder,
|
||||
folder,
|
||||
});
|
||||
}),
|
||||
);
|
||||
@ -135,4 +141,8 @@ export class FileUploadService {
|
||||
paths,
|
||||
};
|
||||
}
|
||||
|
||||
private getWorkspaceFolderName(workspaceId: string, fileFolder: FileFolder) {
|
||||
return `workspace-${workspaceId}/${fileFolder}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import {
|
||||
checkFilename,
|
||||
checkFilePath,
|
||||
} from 'src/engine/core-modules/file/file.utils';
|
||||
|
||||
describe('FileUtils', () => {
|
||||
describe('checkFilePath', () => {
|
||||
it('should return sanitized file path', () => {
|
||||
const filePath = `${FileFolder.Attachment}\0`;
|
||||
const sanitizedFilePath = checkFilePath(filePath);
|
||||
|
||||
expect(sanitizedFilePath).toBe(`${FileFolder.Attachment}`);
|
||||
});
|
||||
|
||||
it('should return sanitized file path with size', () => {
|
||||
const filePath = `${FileFolder.ProfilePicture}\0/original`;
|
||||
const sanitizedFilePath = checkFilePath(filePath);
|
||||
|
||||
expect(sanitizedFilePath).toBe(`${FileFolder.ProfilePicture}/original`);
|
||||
});
|
||||
|
||||
it('should throw an error for invalid image size', () => {
|
||||
const filePath = `${FileFolder.ProfilePicture}\0/invalid-size`;
|
||||
|
||||
expect(() => checkFilePath(filePath)).toThrow(
|
||||
`Size invalid-size is not allowed`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error for invalid folder', () => {
|
||||
const filePath = `invalid-folder`;
|
||||
|
||||
expect(() => checkFilePath(filePath)).toThrow(
|
||||
`Folder invalid-folder is not allowed`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkFilename', () => {
|
||||
it('should return sanitized filename', () => {
|
||||
const filename = `${FileFolder.Attachment}\0.png`;
|
||||
const sanitizedFilename = checkFilename(filename);
|
||||
|
||||
expect(sanitizedFilename).toBe(`${FileFolder.Attachment}.png`);
|
||||
});
|
||||
|
||||
it('should throw an error for invalid filename', () => {
|
||||
const filename = `invalid-filename`;
|
||||
|
||||
expect(() => checkFilename(filename)).toThrow(`Filename is not allowed`);
|
||||
});
|
||||
|
||||
it('should throw an error for invalid filename', () => {
|
||||
const filename = `\0`;
|
||||
|
||||
expect(() => checkFilename(filename)).toThrow(`Filename is not allowed`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -4,8 +4,8 @@ import { basename } from 'path';
|
||||
|
||||
import { KebabCase } from 'type-fest';
|
||||
|
||||
import { kebabCase } from 'src/utils/kebab-case';
|
||||
import { settings } from 'src/engine/constants/settings';
|
||||
import { kebabCase } from 'src/utils/kebab-case';
|
||||
|
||||
import { FileFolder } from './interfaces/file-folder.interface';
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class FilePathGuard implements CanActivate {
|
||||
@ -17,25 +17,34 @@ export class FilePathGuard implements CanActivate {
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const query = context.switchToHttp().getRequest().query;
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const query = request.query;
|
||||
|
||||
if (query && query['token']) {
|
||||
return !(await this.isExpired(query['token']));
|
||||
const payloadToDecode = query['token'];
|
||||
const decodedPayload = await this.tokenService.decodePayload(
|
||||
payloadToDecode,
|
||||
{
|
||||
secret: this.environmentService.get('FILE_TOKEN_SECRET'),
|
||||
},
|
||||
);
|
||||
|
||||
const expirationDate = decodedPayload?.['expiration_date'];
|
||||
const workspaceId = decodedPayload?.['workspace_id'];
|
||||
|
||||
const isExpired = await this.isExpired(expirationDate);
|
||||
|
||||
if (isExpired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
request.workspaceId = workspaceId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async isExpired(signedExpirationDate: string): Promise<boolean> {
|
||||
const decodedPayload = await this.tokenService.decodePayload(
|
||||
signedExpirationDate,
|
||||
{
|
||||
secret: this.environmentService.get('FILE_TOKEN_SECRET'),
|
||||
},
|
||||
);
|
||||
|
||||
const expirationDate = decodedPayload?.['expiration_date'];
|
||||
|
||||
private async isExpired(expirationDate: string): Promise<boolean> {
|
||||
if (!expirationDate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
|
||||
import { FileUploadResolver } from './file-upload.resolver';
|
||||
|
||||
describe('FileUploadResolver', () => {
|
||||
let resolver: FileUploadResolver;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
FileUploadResolver,
|
||||
{
|
||||
provide: FileUploadService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<FileUploadResolver>(FileUploadResolver);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(resolver).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,57 +0,0 @@
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { GraphQLUpload, FileUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
|
||||
@UseGuards(JwtAuthGuard, DemoEnvGuard)
|
||||
@Resolver()
|
||||
export class FileUploadResolver {
|
||||
constructor(private readonly fileUploadService: FileUploadService) {}
|
||||
|
||||
@Mutation(() => String)
|
||||
async uploadFile(
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
@Args('fileFolder', { type: () => FileFolder, nullable: true })
|
||||
fileFolder: FileFolder,
|
||||
): Promise<string> {
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
|
||||
const { path } = await this.fileUploadService.uploadFile({
|
||||
file: buffer,
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
});
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@Mutation(() => String)
|
||||
async uploadImage(
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
@Args('fileFolder', { type: () => FileFolder, nullable: true })
|
||||
fileFolder: FileFolder,
|
||||
): Promise<string> {
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,42 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Stream } from 'stream';
|
||||
|
||||
import {
|
||||
FileStorageException,
|
||||
FileStorageExceptionCode,
|
||||
} from 'src/engine/integrations/file-storage/interfaces/file-storage-exception';
|
||||
|
||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||
|
||||
@Injectable()
|
||||
export class FileService {
|
||||
constructor(private readonly fileStorageService: FileStorageService) {}
|
||||
|
||||
async getFileStream(folderPath: string, filename: string) {
|
||||
return this.fileStorageService.read({
|
||||
folderPath,
|
||||
filename,
|
||||
});
|
||||
async getFileStream(
|
||||
folderPath: string,
|
||||
filename: string,
|
||||
workspaceId: string,
|
||||
): Promise<Stream> {
|
||||
const workspaceFolderPath = `workspace-${workspaceId}/${folderPath}`;
|
||||
|
||||
try {
|
||||
return await this.fileStorageService.read({
|
||||
folderPath: workspaceFolderPath,
|
||||
filename,
|
||||
});
|
||||
} catch (error) {
|
||||
// TODO: Remove this fallback when all files are moved to workspace folders
|
||||
if (
|
||||
error instanceof FileStorageException &&
|
||||
error.code === FileStorageExceptionCode.FILE_NOT_FOUND
|
||||
) {
|
||||
return await this.fileStorageService.read({
|
||||
folderPath,
|
||||
filename,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
} from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import assert from 'assert';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
@ -32,7 +33,6 @@ import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { LoadServiceWithWorkspaceContext } from 'src/engine/twenty-orm/context/load-service-with-workspace.context';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
const getHMACKey = (email?: string, key?: string | null) => {
|
||||
@ -117,6 +117,7 @@ export class UserResolver {
|
||||
@Mutation(() => String)
|
||||
async uploadProfilePicture(
|
||||
@AuthUser() { id }: User,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
): Promise<string> {
|
||||
@ -133,6 +134,7 @@ export class UserResolver {
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
|
||||
@ -85,6 +85,7 @@ export class WorkspaceResolver {
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
workspaceId: id,
|
||||
});
|
||||
|
||||
await this.workspaceService.updateOne(id, {
|
||||
|
||||
Reference in New Issue
Block a user