-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
partial implementation of upload feature to cloudflare r2
- Loading branch information
1 parent
0497e97
commit 0adf80d
Showing
22 changed files
with
3,135 additions
and
191 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
src/domain/forum/application/repositories/attachments-repository.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Attachment } from '../../enterprise/entities/attachment'; | ||
|
||
export abstract class IAttachmentsRepository { | ||
abstract create(attachment: Attachment): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface UploadParams { | ||
fileName: string; | ||
fileType: string; | ||
body: Buffer; | ||
} | ||
|
||
export abstract class Uploader { | ||
abstract upload(params: UploadParams): Promise<{ url: string }>; | ||
} |
47 changes: 47 additions & 0 deletions
47
src/domain/forum/application/use-cases/upload-and-create-attachment.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { InMemoryAttachmentsRepository } from 'test/repositories/in-memory-attachments-repository'; | ||
import { UploadAndCreateAttachmentUseCase } from './upload-and-create-attachment'; | ||
import { FakeUploader } from 'test/storage/fake-uploader'; | ||
import { InvalidAttachmentTypeError } from '@/core/errors/custom-errors'; | ||
|
||
let repository: InMemoryAttachmentsRepository; | ||
let uploader: FakeUploader; | ||
let sut: UploadAndCreateAttachmentUseCase; | ||
|
||
describe('Upload and create attachment tests', () => { | ||
beforeEach(() => { | ||
repository = new InMemoryAttachmentsRepository(); | ||
uploader = new FakeUploader(); | ||
sut = new UploadAndCreateAttachmentUseCase(repository, uploader); | ||
}); | ||
|
||
it('should be able to upload a new attachment', async () => { | ||
const result = await sut.execute({ | ||
fileName: 'document.png', | ||
fileType: 'image/png', | ||
body: Buffer.from('') | ||
}); | ||
|
||
expect(result.isRight()).toBe(true); | ||
expect(result.value).toEqual({ | ||
attachment: repository.items[0], | ||
}); | ||
expect(uploader.uploads).toHaveLength(1); | ||
expect(uploader.uploads[0]).toEqual( | ||
expect.objectContaining({ | ||
fileName: 'document.png' | ||
}), | ||
); | ||
}); | ||
|
||
it('should NOT be able to upload a new attachment with wrong mime type', async () => { | ||
const result = await sut.execute({ | ||
fileName: 'document.txt', | ||
fileType: 'text/plain', | ||
body: Buffer.from('') | ||
}); | ||
|
||
expect(result.isLeft()).toBe(true); | ||
expect(result.value).toBeInstanceOf(InvalidAttachmentTypeError); | ||
expect(uploader.uploads).toHaveLength(0); | ||
}); | ||
}); |
52 changes: 52 additions & 0 deletions
52
src/domain/forum/application/use-cases/upload-and-create-attachment.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { Either, left, right } from '@/core/either'; | ||
import { Injectable } from '@nestjs/common'; | ||
import { InvalidAttachmentTypeError } from '@/core/errors/custom-errors'; | ||
import { Attachment } from '../../enterprise/entities/attachment'; | ||
import { IAttachmentsRepository } from '../repositories/attachments-repository'; | ||
import { Uploader } from '../storage/uploader'; | ||
|
||
interface UploadAndCreateAttachmentUseCaseRequest { | ||
fileName: string; | ||
fileType: string; | ||
body: Buffer; | ||
} | ||
|
||
type UploadAndCreateAttachmentUseCaseResponse = Either< | ||
InvalidAttachmentTypeError, | ||
{ | ||
attachment: Attachment | ||
} | ||
>; | ||
|
||
@Injectable() | ||
export class UploadAndCreateAttachmentUseCase { | ||
constructor( | ||
private attachmentsRepository: IAttachmentsRepository, | ||
private uploader: Uploader | ||
){} | ||
|
||
async execute({ | ||
fileName, | ||
fileType, | ||
body | ||
}: UploadAndCreateAttachmentUseCaseRequest): Promise<UploadAndCreateAttachmentUseCaseResponse> { | ||
if (!/^(image\/(jpeg|png))$|^application\/pdf$/.test(fileType)) { | ||
return left(new InvalidAttachmentTypeError()); | ||
} | ||
|
||
const { url } = await this.uploader.upload({ | ||
fileName, | ||
fileType, | ||
body | ||
}); | ||
|
||
const attachment = Attachment.create({ | ||
title: fileName, | ||
url, | ||
}); | ||
|
||
await this.attachmentsRepository.create(attachment); | ||
|
||
return right({ attachment }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
src/infra/database/prisma/mappers/prisma-attachments-mapper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Attachment } from '@forum-entities/attachment'; | ||
import { Prisma } from '@prisma/client'; | ||
|
||
export class PrismaAttachmentsMapper { | ||
static toDatabase(raw: Attachment): Prisma.AttachmentUncheckedCreateInput { | ||
return { | ||
id: raw.id.toString(), | ||
title: raw.title, | ||
url: raw.url | ||
}; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
src/infra/database/prisma/repositories/prisma-attachments-repository.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { IAttachmentsRepository } from '@forum-repositories/attachments-repository'; | ||
import { Attachment } from '@forum-entities/attachment'; | ||
import { PrismaService } from '../prisma.service'; | ||
import { PrismaAttachmentsMapper } from '../mappers/prisma-attachments-mapper'; | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
@Injectable() | ||
export class PrismaAttachmentsRepository implements IAttachmentsRepository { | ||
constructor( | ||
private prisma: PrismaService | ||
){} | ||
|
||
async create(attachment: Attachment): Promise<void> { | ||
const data = PrismaAttachmentsMapper.toDatabase(attachment); | ||
|
||
await this.prisma.attachment.create({ | ||
data | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
src/infra/http/controllers/upload-attachment.controller.e2e-spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { AppModule } from '@/infra/app.module'; | ||
import { DatabaseModule } from '@/infra/database/database.module'; | ||
import { INestApplication } from '@nestjs/common'; | ||
import { JwtService } from '@nestjs/jwt'; | ||
import { Test } from '@nestjs/testing'; | ||
import request from 'supertest'; | ||
import { StudentFactory } from 'test/factories/make-student'; | ||
|
||
describe('[e2e] upload attachment tests', () => { | ||
let app: INestApplication; | ||
let jwt: JwtService; | ||
let studentFactory: StudentFactory; | ||
|
||
beforeAll(async () => { | ||
const moduleRef = await Test.createTestingModule({ | ||
imports: [AppModule, DatabaseModule], | ||
providers: [StudentFactory], | ||
}).compile(); | ||
|
||
app = moduleRef.createNestApplication(); | ||
jwt = moduleRef.get(JwtService); | ||
studentFactory = moduleRef.get(StudentFactory); | ||
|
||
await app.init(); | ||
}); | ||
|
||
it('[POST] /attachments', async () => { | ||
const user = await studentFactory.makePrismaStudent(); | ||
|
||
const accessToken = jwt.sign({ sub: user.id.toString() }); | ||
|
||
const response = await request(app.getHttpServer()) | ||
.post('/attachments') | ||
.set('Authorization', `Bearer ${accessToken}`) | ||
.attach('file', './test/e2e/document.pdf'); | ||
|
||
expect(response.statusCode).toBe(201); | ||
expect(response.body).toEqual({ | ||
attachmentId: expect.any(String) | ||
}); | ||
}); | ||
}); |
63 changes: 63 additions & 0 deletions
63
src/infra/http/controllers/upload-attachment.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { InvalidAttachmentTypeError } from '@/core/errors/custom-errors'; | ||
import { UploadAndCreateAttachmentUseCase } from '@forum-use-cases/upload-and-create-attachment'; | ||
import { | ||
BadRequestException, | ||
Controller, | ||
FileTypeValidator, | ||
HttpCode, | ||
MaxFileSizeValidator, | ||
ParseFilePipe, | ||
Post, | ||
UnsupportedMediaTypeException, | ||
UploadedFile, | ||
UseInterceptors | ||
} from '@nestjs/common'; | ||
import { FileInterceptor } from '@nestjs/platform-express'; | ||
|
||
@Controller('/attachments') | ||
export class UploadAttachmentController { | ||
|
||
constructor( | ||
private uploadAndCreateAttachment: UploadAndCreateAttachmentUseCase | ||
){} | ||
|
||
@Post() | ||
@HttpCode(201) | ||
@UseInterceptors(FileInterceptor('file')) | ||
async handle ( | ||
@UploadedFile( | ||
new ParseFilePipe({ | ||
validators: [ | ||
new MaxFileSizeValidator({ | ||
maxSize: 1024 * 1024 * 2 //2mb | ||
}), | ||
new FileTypeValidator({ fileType: '.(png|jpg|jpeg|pdf)' }), | ||
], | ||
}), | ||
) | ||
file: Express.Multer.File | ||
){ | ||
const result = await this.uploadAndCreateAttachment.execute({ | ||
fileName: file.originalname, | ||
fileType: file.mimetype, | ||
body: file.buffer | ||
}); | ||
|
||
if (result.isLeft()) { | ||
const error = result.value; | ||
|
||
switch (error.constructor) { | ||
case InvalidAttachmentTypeError: | ||
throw new UnsupportedMediaTypeException(error.message); | ||
default: | ||
throw new BadRequestException(error.message); | ||
} | ||
} | ||
|
||
const { attachment } = result.value; | ||
|
||
return { | ||
attachmentId: attachment.id.toString() | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.