Skip to content

Commit

Permalink
Merge pull request #46 from MatheusSanchez/add-profile-photo-user
Browse files Browse the repository at this point in the history
Upload user photo on S3
  • Loading branch information
pedrodecf authored Feb 3, 2024
2 parents 8745965 + 085066c commit 88670e2
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/controller/user/addImageToUser.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
10 changes: 10 additions & 0 deletions src/controller/user/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 4 additions & 0 deletions src/repositories/in-memory-db/inMemoryUserRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ export class InMemoryUserRepository implements UserRepository {
this.db.push(user)
return user
}

async addPhotoUrl(projectId: string, photoUrl: string): Promise<Project> {
throw new Error('Method not implemented.')
}
}
13 changes: 13 additions & 0 deletions src/repositories/prisma/prisma-users-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@ export class PrismaUsersRepository implements UserRepository {

return user
}

async addPhotoUrl(userId: string, photoUrl: string): Promise<User> {
const user = await prisma.user.update({
where: {
id: userId,
},
data: {
avatar_url: photoUrl,
},
})

return user
}
}
1 change: 1 addition & 0 deletions src/repositories/user-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface UserRepository {
create(data: Prisma.UserCreateInput): Promise<User>
findByEmail(email: string): Promise<User | null>
findById(id: string): Promise<User | null>
addPhotoUrl(userId: string, photoUrl: string): Promise<User>
}
74 changes: 74 additions & 0 deletions src/use-cases/user/addImageToUserUseCase.ts
Original file line number Diff line number Diff line change
@@ -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<AddImageToUserUseCaseResponse> {
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 }
}
}

0 comments on commit 88670e2

Please sign in to comment.