From 085066ccf5fb7aa0863b248c7795d395b1429d1e Mon Sep 17 00:00:00 2001 From: Matheus Sanchez Date: Sat, 3 Feb 2024 17:04:10 -0300 Subject: [PATCH] Handle upload files local and S3 aws --- src/controller/user/addImageToUser.ts | 36 +++++++++ src/controller/user/routes.ts | 10 +++ .../in-memory-db/inMemoryUserRepository.ts | 4 + .../prisma/prisma-users-repository.ts | 13 ++++ src/repositories/user-repository.ts | 1 + src/use-cases/user/addImageToUserUseCase.ts | 74 +++++++++++++++++++ 6 files changed, 138 insertions(+) create mode 100644 src/controller/user/addImageToUser.ts create mode 100644 src/use-cases/user/addImageToUserUseCase.ts diff --git a/src/controller/user/addImageToUser.ts b/src/controller/user/addImageToUser.ts new file mode 100644 index 0000000..2121206 --- /dev/null +++ b/src/controller/user/addImageToUser.ts @@ -0,0 +1,36 @@ +import { FastifyReply, FastifyRequest } from 'fastify' +import { z } from 'zod' +import { ResourceNotFoundError } from '../../use-cases/errors/ResourceNotFoundError' +import { AwsS3Error } from '../../use-cases/errors/AwsS3Error' +import { PrismaUsersRepository } from '../../repositories/prisma/prisma-users-repository' +import { AddImageToUserUseCase } from '../../use-cases/user/addImageToUserUseCase' + +export async function addImageUser( + request: FastifyRequest, + response: FastifyReply, +) { + const userRepository = new PrismaUsersRepository() + const addImageToUserUseCase = new AddImageToUserUseCase(userRepository) + const addImageUserParamsSchema = z.object({ + userId: z.string().uuid(), + }) + + const { userId } = addImageUserParamsSchema.parse(request.params) + const photo = await request.file() + + if (photo === undefined) { + return response.status(400).send({ error: 'Fail load a photo!' }) + } + + try { + const { user } = await addImageToUserUseCase.execute({ userId, photo }) + return response.status(200).send({ user }) + } catch (error) { + if (error instanceof ResourceNotFoundError) { + return response.status(400).send({ error: 'User was not found !' }) + } else if (error instanceof AwsS3Error) { + return response.status(400).send({ error: error.message }) + } + throw error + } +} diff --git a/src/controller/user/routes.ts b/src/controller/user/routes.ts index 941a5ed..210da29 100644 --- a/src/controller/user/routes.ts +++ b/src/controller/user/routes.ts @@ -2,9 +2,19 @@ import { FastifyInstance } from 'fastify' import { getUserById } from './getUserById' import { getUserByEmail } from './getUserByEmail' import { registerUser } from './registerUser' +import { addImageUser } from './addImageToUser' +import FastifyMultipart from '@fastify/multipart' export async function userRoutes(app: FastifyInstance) { + app.register(FastifyMultipart, { + limits: { + files: 1, + fileSize: 1000000, // the max file size in bytes + }, + }) app.post('/user', registerUser) app.get('/user/:id', getUserById) app.get('/user', getUserByEmail) + + app.post('/user/:userId/photo', addImageUser) } diff --git a/src/repositories/in-memory-db/inMemoryUserRepository.ts b/src/repositories/in-memory-db/inMemoryUserRepository.ts index 0aa5088..8a735f9 100644 --- a/src/repositories/in-memory-db/inMemoryUserRepository.ts +++ b/src/repositories/in-memory-db/inMemoryUserRepository.ts @@ -52,4 +52,8 @@ export class InMemoryUserRepository implements UserRepository { this.db.push(user) return user } + + async addPhotoUrl(projectId: string, photoUrl: string): Promise { + throw new Error('Method not implemented.') + } } diff --git a/src/repositories/prisma/prisma-users-repository.ts b/src/repositories/prisma/prisma-users-repository.ts index e43197d..468e366 100644 --- a/src/repositories/prisma/prisma-users-repository.ts +++ b/src/repositories/prisma/prisma-users-repository.ts @@ -30,4 +30,17 @@ export class PrismaUsersRepository implements UserRepository { return user } + + async addPhotoUrl(userId: string, photoUrl: string): Promise { + const user = await prisma.user.update({ + where: { + id: userId, + }, + data: { + avatar_url: photoUrl, + }, + }) + + return user + } } diff --git a/src/repositories/user-repository.ts b/src/repositories/user-repository.ts index 2a53acb..e6419b2 100644 --- a/src/repositories/user-repository.ts +++ b/src/repositories/user-repository.ts @@ -4,4 +4,5 @@ export interface UserRepository { create(data: Prisma.UserCreateInput): Promise findByEmail(email: string): Promise findById(id: string): Promise + addPhotoUrl(userId: string, photoUrl: string): Promise } diff --git a/src/use-cases/user/addImageToUserUseCase.ts b/src/use-cases/user/addImageToUserUseCase.ts new file mode 100644 index 0000000..213238e --- /dev/null +++ b/src/use-cases/user/addImageToUserUseCase.ts @@ -0,0 +1,74 @@ +import { User } from '@prisma/client' + +import { ResourceNotFoundError } from '../errors/ResourceNotFoundError' +import { MultipartFile } from '@fastify/multipart' +import { randomUUID } from 'node:crypto' +import { env } from '../../env' +import path from 'node:path' +import fs from 'node:fs' +import { PutObjectCommand, S3Client, S3ClientConfig } from '@aws-sdk/client-s3' +import pump from 'pump' +import { AwsS3Error } from '../errors/AwsS3Error' +import { UserRepository } from '../../repositories/user-repository' + +interface AddImageToUserUseCaseRequest { + userId: string + photo: MultipartFile +} + +interface AddImageToUserUseCaseResponse { + user: User +} + +export class AddImageToUserUseCase { + constructor(private userRepository: UserRepository) {} + + async execute({ + userId, + photo, + }: AddImageToUserUseCaseRequest): Promise { + const userToBeUpdated = await this.userRepository.findById(userId) + + if (!userToBeUpdated) { + throw new ResourceNotFoundError() + } + + const newFileName = randomUUID() + photo.filename.replace(/\s/g, '') + let photoUrl = '' + + if (env.STORAGE_TYPE === 'local') { + const uploadPath = path.resolve(__dirname, '..', 'tmp', 'uploads') + const writeSteam = fs.createWriteStream(`${uploadPath}/${newFileName}`) + await pump(photo.file, writeSteam) + photoUrl = `${uploadPath}/${newFileName}` + } else { + const s3bucket = new S3Client({ + region: env.REGION, + + credentials: { + accessKeyId: env.ACCESS_KEY_ID, + secretAccessKey: env.SECRET_ACCESS_KEY, + }, + } as S3ClientConfig) + + const putObjectCommand = new PutObjectCommand({ + Bucket: env.BUCKET_NAME, + Key: newFileName, + Body: await photo.toBuffer(), + ContentType: photo.mimetype, + ACL: 'public-read', + }) + + const publishs3Result = await s3bucket.send(putObjectCommand) + + if (publishs3Result.$metadata.httpStatusCode !== 200) { + throw new AwsS3Error() + } + photoUrl = env.AWS_S3_URL + newFileName + } + + const user = await this.userRepository.addPhotoUrl(userId, photoUrl) + + return { user } + } +}