Add attachments (#733)

* Add attachments v1

* Refacto

* Add Policy checks

* Fix tests

* Remove generated files from git

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Félix Malfait
2023-07-19 00:24:03 +02:00
committed by GitHub
parent 84018efc7d
commit 10f7b08fdc
1130 changed files with 570 additions and 39762 deletions

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { AttachmentResolver } from './resolvers/attachment.resolver';
import { FileUploadService } from '../file/services/file-upload.service';
import { AttachmentService } from './services/attachment.service';
@Module({
providers: [AttachmentService, AttachmentResolver, FileUploadService],
exports: [AttachmentService],
})
export class AttachmentModule {}

View File

@ -0,0 +1,35 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AttachmentResolver } from './attachment.resolver';
import { FileUploadService } from 'src/core/file/services/file-upload.service';
import { AttachmentService } from '../services/attachment.service';
import { AbilityFactory } from 'src/ability/ability.factory';
describe('AttachmentResolver', () => {
let resolver: AttachmentResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AttachmentResolver,
{
provide: FileUploadService,
useValue: {},
},
{
provide: AttachmentService,
useValue: {},
},
{
provide: AbilityFactory,
useValue: {},
},
],
}).compile();
resolver = module.get<AttachmentResolver>(AttachmentResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -0,0 +1,65 @@
import { Resolver, Args, Mutation } from '@nestjs/graphql';
import { User, Workspace } from '@prisma/client';
import { GraphQLUpload, FileUpload } from 'graphql-upload';
import { AuthUser } from 'src/decorators/auth-user.decorator';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import { streamToBuffer } from 'src/utils/stream-to-buffer';
import { v4 as uuidV4 } from 'uuid';
import { AttachmentService } from '../services/attachment.service';
import { FileUploadService } from 'src/core/file/services/file-upload.service';
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { Attachment } from 'src/core/@generated/attachment/attachment.model';
import { AbilityGuard } from 'src/guards/ability.guard';
import { CreateAttachmentAbilityHandler } from 'src/ability/handlers/attachment.ability-handler';
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => Attachment)
@Resolver()
export class AttachmentResolver {
constructor(
private readonly fileUploadService: FileUploadService,
private readonly attachmentService: AttachmentService,
) {}
@UseGuards(AbilityGuard)
@CheckAbilities(CreateAttachmentAbilityHandler)
@Mutation(() => String)
async uploadAttachment(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
@Args('activityId') activityId: string,
@Args({ name: 'file', type: () => GraphQLUpload })
{ createReadStream, filename, mimetype }: FileUpload,
): Promise<string> {
const stream = createReadStream();
const buffer = await streamToBuffer(stream);
const { path } = await this.fileUploadService.uploadFile({
file: buffer,
filename,
mimeType: mimetype,
fileFolder: FileFolder.Attachment,
});
await this.attachmentService.create({
data: {
id: uuidV4(),
fullPath: path,
type: this.attachmentService.getFileTypeFromFileName(filename),
name: filename,
activityId: activityId,
authorId: user.id,
workspaceId: workspace.id,
},
select: {
id: true,
fullPath: true,
},
});
return path;
}
}

View File

@ -0,0 +1,26 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PrismaService } from 'src/database/prisma.service';
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
import { AttachmentService } from './attachment.service';
describe('AttachmentService', () => {
let service: AttachmentService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AttachmentService,
{
provide: PrismaService,
useValue: prismaMock,
},
],
}).compile();
service = module.get<AttachmentService>(AttachmentService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,81 @@
import { Injectable } from '@nestjs/common';
import { AttachmentType } from '@prisma/client';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class AttachmentService {
constructor(private readonly prismaService: PrismaService) {}
// Find
findFirst = this.prismaService.attachment.findFirst;
findFirstOrThrow = this.prismaService.attachment.findFirstOrThrow;
findUnique = this.prismaService.attachment.findUnique;
findUniqueOrThrow = this.prismaService.attachment.findUniqueOrThrow;
findMany = this.prismaService.attachment.findMany;
// Create
create = this.prismaService.attachment.create;
createMany = this.prismaService.attachment.createMany;
// Update
update = this.prismaService.attachment.update;
upsert = this.prismaService.attachment.upsert;
updateMany = this.prismaService.attachment.updateMany;
// Delete
delete = this.prismaService.attachment.delete;
deleteMany = this.prismaService.attachment.deleteMany;
// Aggregate
aggregate = this.prismaService.attachment.aggregate;
// Count
count = this.prismaService.attachment.count;
// GroupBy
groupBy = this.prismaService.attachment.groupBy;
getFileTypeFromFileName(fileName: string): AttachmentType {
const extension = fileName.split('.').pop()?.toLowerCase();
switch (extension) {
case 'mp4':
case 'avi':
case 'mov':
return AttachmentType.Video;
case 'mp3':
case 'wav':
case 'ogg':
return AttachmentType.Audio;
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return AttachmentType.Image;
case 'txt':
case 'doc':
case 'docx':
case 'pdf':
return AttachmentType.TextDocument;
case 'xls':
case 'xlsx':
case 'csv':
return AttachmentType.Spreadsheet;
case 'zip':
case 'rar':
case 'tar':
case '7z':
return AttachmentType.Archive;
default:
return AttachmentType.Other;
}
}
}