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:
10
server/src/core/attachment/attachment.module.ts
Normal file
10
server/src/core/attachment/attachment.module.ts
Normal 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 {}
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
65
server/src/core/attachment/resolvers/attachment.resolver.ts
Normal file
65
server/src/core/attachment/resolvers/attachment.resolver.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
81
server/src/core/attachment/services/attachment.service.ts
Normal file
81
server/src/core/attachment/services/attachment.service.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user