From 7355306aa10791c68696dd9a248279362c306348 Mon Sep 17 00:00:00 2001 From: Artur Poffo Date: Wed, 28 Feb 2024 01:43:59 -0300 Subject: [PATCH] Feat(Image, Video): Get video/image details by id route --- .../use-cases/get-image-details-by-id.spec.ts | 42 +++++++++++++++++++ .../use-cases/get-image-details-by-id.ts | 36 ++++++++++++++++ .../use-cases/get-image-details.spec.ts | 2 +- .../use-cases/get-video-details-by-id.spec.ts | 42 +++++++++++++++++++ .../use-cases/get-video-details-by-id.ts | 36 ++++++++++++++++ .../use-cases/get-video-details.spec.ts | 2 +- .../controllers/get-image-details-by-id.ts | 37 ++++++++++++++++ .../controllers/get-video-details-by-id.ts | 37 ++++++++++++++++ src/infra/http/routes/image.ts | 4 +- src/infra/http/routes/video.ts | 4 +- .../make-get-image-details-by-id-use-case.ts | 12 ++++++ .../make-get-video-details-by-id-use-case.ts | 12 ++++++ 12 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 src/domain/course-management/application/use-cases/get-image-details-by-id.spec.ts create mode 100644 src/domain/course-management/application/use-cases/get-image-details-by-id.ts create mode 100644 src/domain/course-management/application/use-cases/get-video-details-by-id.spec.ts create mode 100644 src/domain/course-management/application/use-cases/get-video-details-by-id.ts create mode 100644 src/infra/http/controllers/get-image-details-by-id.ts create mode 100644 src/infra/http/controllers/get-video-details-by-id.ts create mode 100644 src/infra/use-cases/factories/make-get-image-details-by-id-use-case.ts create mode 100644 src/infra/use-cases/factories/make-get-video-details-by-id-use-case.ts diff --git a/src/domain/course-management/application/use-cases/get-image-details-by-id.spec.ts b/src/domain/course-management/application/use-cases/get-image-details-by-id.spec.ts new file mode 100644 index 0000000..142290d --- /dev/null +++ b/src/domain/course-management/application/use-cases/get-image-details-by-id.spec.ts @@ -0,0 +1,42 @@ +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +import { makeImage } from '../../../../../test/factories/make-image' +import { InMemoryImagesRepository } from './../../../../../test/repositories/in-memory-images-repository' +import { GetImageDetailsByIdByIdUseCase } from './get-image-details-by-id' + +let inMemoryImagesRepository: InMemoryImagesRepository +let sut: GetImageDetailsByIdByIdUseCase + +describe('Get image details use case', async () => { + beforeEach(() => { + inMemoryImagesRepository = new InMemoryImagesRepository() + sut = new GetImageDetailsByIdByIdUseCase(inMemoryImagesRepository) + }) + + it('should be able to get image details', async () => { + const name = 'john-doe-image.jpeg' + + const image = makeImage({ imageName: name, imageKey: '23323232-image.jpeg' }) + + await inMemoryImagesRepository.create(image) + + const result = await sut.exec({ + imageId: image.id.toString() + }) + + expect(result.isRight()).toBe(true) + expect(result.value).toMatchObject({ + image: expect.objectContaining({ + imageName: name + }) + }) + }) + + it('should not be able to get image details of a inexistent image', async () => { + const result = await sut.exec({ + imageId: 'inexistentImageId' + }) + + expect(result.isLeft()).toBe(true) + expect(result.value).toBeInstanceOf(ResourceNotFoundError) + }) +}) diff --git a/src/domain/course-management/application/use-cases/get-image-details-by-id.ts b/src/domain/course-management/application/use-cases/get-image-details-by-id.ts new file mode 100644 index 0000000..5f94192 --- /dev/null +++ b/src/domain/course-management/application/use-cases/get-image-details-by-id.ts @@ -0,0 +1,36 @@ +import { left, right, type Either } from '@/core/either' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +import { type UseCase } from '@/core/use-cases/use-case' +import { type Image } from '../../enterprise/entities/image' +import { type ImagesRepository } from '../repositories/images-repository' + +interface GetImageDetailsByIdUseCaseRequest { + imageId: string +} + +type GetImageDetailsByIdUseCaseResponse = Either< +ResourceNotFoundError, +{ + image: Image +} +> + +export class GetImageDetailsByIdByIdUseCase implements UseCase { + constructor( + private readonly imagesRepository: ImagesRepository + ) { } + + async exec({ + imageId + }: GetImageDetailsByIdUseCaseRequest): Promise { + const image = await this.imagesRepository.findById(imageId) + + if (!image) { + return left(new ResourceNotFoundError()) + } + + return right({ + image + }) + } +} diff --git a/src/domain/course-management/application/use-cases/get-image-details.spec.ts b/src/domain/course-management/application/use-cases/get-image-details.spec.ts index fffbf7a..ff887a5 100644 --- a/src/domain/course-management/application/use-cases/get-image-details.spec.ts +++ b/src/domain/course-management/application/use-cases/get-image-details.spec.ts @@ -33,7 +33,7 @@ describe('Get image details use case', async () => { it('should not be able to get image details of a inexistent image', async () => { const result = await sut.exec({ - fileKey: 'inexistentImageId' + fileKey: 'inexistentImageKey' }) expect(result.isLeft()).toBe(true) diff --git a/src/domain/course-management/application/use-cases/get-video-details-by-id.spec.ts b/src/domain/course-management/application/use-cases/get-video-details-by-id.spec.ts new file mode 100644 index 0000000..505b6aa --- /dev/null +++ b/src/domain/course-management/application/use-cases/get-video-details-by-id.spec.ts @@ -0,0 +1,42 @@ +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +import { makeVideo } from '../../../../../test/factories/make-video' +import { InMemoryVideosRepository } from './../../../../../test/repositories/in-memory-videos-repository' +import { GetVideoDetailsByIdUseCase } from './get-video-details-by-id' + +let inMemoryVideosRepository: InMemoryVideosRepository +let sut: GetVideoDetailsByIdUseCase + +describe('Get video details use case', async () => { + beforeEach(() => { + inMemoryVideosRepository = new InMemoryVideosRepository() + sut = new GetVideoDetailsByIdUseCase(inMemoryVideosRepository) + }) + + it('should be able to get video details', async () => { + const name = 'john-doe-video.mp4' + + const video = makeVideo({ videoName: name, videoKey: '2332323-video.mp4' }) + + await inMemoryVideosRepository.create(video) + + const result = await sut.exec({ + videoId: video.id.toString() + }) + + expect(result.isRight()).toBe(true) + expect(result.value).toMatchObject({ + video: expect.objectContaining({ + videoName: name + }) + }) + }) + + it('should not be able to get video details of a inexistent video', async () => { + const result = await sut.exec({ + videoId: 'inexistentVideoId' + }) + + expect(result.isLeft()).toBe(true) + expect(result.value).toBeInstanceOf(ResourceNotFoundError) + }) +}) diff --git a/src/domain/course-management/application/use-cases/get-video-details-by-id.ts b/src/domain/course-management/application/use-cases/get-video-details-by-id.ts new file mode 100644 index 0000000..1e0ca18 --- /dev/null +++ b/src/domain/course-management/application/use-cases/get-video-details-by-id.ts @@ -0,0 +1,36 @@ +import { left, right, type Either } from '@/core/either' +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +import { type UseCase } from '@/core/use-cases/use-case' +import { type Video } from '../../enterprise/entities/video' +import { type VideosRepository } from '../repositories/videos-repository' + +interface GetVideoDetailsByIdUseCaseRequest { + videoId: string +} + +type GetVideoDetailsByIdUseCaseResponse = Either< +ResourceNotFoundError, +{ + video: Video +} +> + +export class GetVideoDetailsByIdUseCase implements UseCase { + constructor( + private readonly videosRepository: VideosRepository + ) { } + + async exec({ + videoId + }: GetVideoDetailsByIdUseCaseRequest): Promise { + const video = await this.videosRepository.findById(videoId) + + if (!video) { + return left(new ResourceNotFoundError()) + } + + return right({ + video + }) + } +} diff --git a/src/domain/course-management/application/use-cases/get-video-details.spec.ts b/src/domain/course-management/application/use-cases/get-video-details.spec.ts index 86d3b8f..e19ec60 100644 --- a/src/domain/course-management/application/use-cases/get-video-details.spec.ts +++ b/src/domain/course-management/application/use-cases/get-video-details.spec.ts @@ -33,7 +33,7 @@ describe('Get video details use case', async () => { it('should not be able to get video details of a inexistent video', async () => { const result = await sut.exec({ - fileKey: 'inexistentVideoId' + fileKey: 'inexistentVideoKey' }) expect(result.isLeft()).toBe(true) diff --git a/src/infra/http/controllers/get-image-details-by-id.ts b/src/infra/http/controllers/get-image-details-by-id.ts new file mode 100644 index 0000000..ca8df76 --- /dev/null +++ b/src/infra/http/controllers/get-image-details-by-id.ts @@ -0,0 +1,37 @@ +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +import { ImageMapper } from '@/infra/database/prisma/mappers/image-mapper' +import { makeGetImageDetailsByIdUseCase } from '@/infra/use-cases/factories/make-get-image-details-by-id-use-case' +import { type FastifyReply, type FastifyRequest } from 'fastify' +import { z } from 'zod' +import { ImagePresenter } from '../presenters/image-presenter' + +const getImageDetailsByIdParamsSchema = z.object({ + imageId: z.string() +}) + +export async function getImageDetailsByIdController(request: FastifyRequest, reply: FastifyReply) { + const { imageId } = getImageDetailsByIdParamsSchema.parse(request.params) + + const getImageInfoUseCase = makeGetImageDetailsByIdUseCase() + + const result = await getImageInfoUseCase.exec({ + imageId + }) + + if (result.isLeft()) { + const error = result.value + + switch (error.constructor) { + case ResourceNotFoundError: + return await reply.status(404).send({ message: error.message }) + default: + return await reply.status(500).send({ message: error.message }) + } + } + + const image = ImageMapper.toPrisma(result.value.image) + + return await reply.status(200).send({ + image: ImagePresenter.toHTTP(image) + }) +} diff --git a/src/infra/http/controllers/get-video-details-by-id.ts b/src/infra/http/controllers/get-video-details-by-id.ts new file mode 100644 index 0000000..479b31c --- /dev/null +++ b/src/infra/http/controllers/get-video-details-by-id.ts @@ -0,0 +1,37 @@ +import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' +import { VideoMapper } from '@/infra/database/prisma/mappers/video-mapper' +import { makeGetVideoDetailsByIdUseCase } from '@/infra/use-cases/factories/make-get-video-details-by-id-use-case' +import { type FastifyReply, type FastifyRequest } from 'fastify' +import { z } from 'zod' +import { VideoPresenter } from '../presenters/video-presenter' + +const getVideoDetailsByIdParamsSchema = z.object({ + videoId: z.string() +}) + +export async function getVideoDetailsByIdController(request: FastifyRequest, reply: FastifyReply) { + const { videoId } = getVideoDetailsByIdParamsSchema.parse(request.params) + + const getVideoInfoUseCase = makeGetVideoDetailsByIdUseCase() + + const result = await getVideoInfoUseCase.exec({ + videoId + }) + + if (result.isLeft()) { + const error = result.value + + switch (error.constructor) { + case ResourceNotFoundError: + return await reply.status(404).send({ message: error.message }) + default: + return await reply.status(500).send({ message: error.message }) + } + } + + const video = VideoMapper.toPrisma(result.value.video) + + return await reply.status(200).send({ + video: VideoPresenter.toHTTP(video) + }) +} diff --git a/src/infra/http/routes/image.ts b/src/infra/http/routes/image.ts index c65967a..6c50c63 100644 --- a/src/infra/http/routes/image.ts +++ b/src/infra/http/routes/image.ts @@ -1,7 +1,9 @@ import { type FastifyInstance } from 'fastify' import { getImageDetailsController } from '../controllers/get-image-details' +import { getImageDetailsByIdController } from '../controllers/get-image-details-by-id' import { verifyJwt } from '../middlewares/verify-jwt' export async function imageRoutes(app: FastifyInstance) { - app.get('/images/:fileKey', { onRequest: [verifyJwt] }, getImageDetailsController) + app.get('/images/find/key/:fileKey', { onRequest: [verifyJwt] }, getImageDetailsController) + app.get('/images/find/id/:imageId', { onRequest: [verifyJwt] }, getImageDetailsByIdController) } diff --git a/src/infra/http/routes/video.ts b/src/infra/http/routes/video.ts index 42df02e..5193810 100644 --- a/src/infra/http/routes/video.ts +++ b/src/infra/http/routes/video.ts @@ -1,7 +1,9 @@ import { type FastifyInstance } from 'fastify' import { getVideoDetailsController } from '../controllers/get-video-details' +import { getVideoDetailsByIdController } from '../controllers/get-video-details-by-id' import { verifyJwt } from '../middlewares/verify-jwt' export async function videoRoutes(app: FastifyInstance) { - app.get('/videos/:fileKey', { onRequest: [verifyJwt] }, getVideoDetailsController) + app.get('/videos/find/key/:fileKey', { onRequest: [verifyJwt] }, getVideoDetailsController) + app.get('/videos/find/id/:videoId', { onRequest: [verifyJwt] }, getVideoDetailsByIdController) } diff --git a/src/infra/use-cases/factories/make-get-image-details-by-id-use-case.ts b/src/infra/use-cases/factories/make-get-image-details-by-id-use-case.ts new file mode 100644 index 0000000..7c251ef --- /dev/null +++ b/src/infra/use-cases/factories/make-get-image-details-by-id-use-case.ts @@ -0,0 +1,12 @@ +import { GetImageDetailsByIdByIdUseCase } from '@/domain/course-management/application/use-cases/get-image-details-by-id' +import { PrismaImagesRepository } from '@/infra/database/prisma/repositories/prisma-images-repository' + +export function makeGetImageDetailsByIdUseCase() { + const prismaImagesRepository = new PrismaImagesRepository() + + const getImageDetailsByIdUseCase = new GetImageDetailsByIdByIdUseCase( + prismaImagesRepository + ) + + return getImageDetailsByIdUseCase +} diff --git a/src/infra/use-cases/factories/make-get-video-details-by-id-use-case.ts b/src/infra/use-cases/factories/make-get-video-details-by-id-use-case.ts new file mode 100644 index 0000000..15e3bdc --- /dev/null +++ b/src/infra/use-cases/factories/make-get-video-details-by-id-use-case.ts @@ -0,0 +1,12 @@ +import { GetVideoDetailsByIdUseCase } from '@/domain/course-management/application/use-cases/get-video-details-by-id' +import { PrismaVideosRepository } from '@/infra/database/prisma/repositories/prisma-videos-repository' + +export function makeGetVideoDetailsByIdUseCase() { + const prismaVideosRepository = new PrismaVideosRepository() + + const getVideoDetailsByIdUseCase = new GetVideoDetailsByIdUseCase( + prismaVideosRepository + ) + + return getVideoDetailsByIdUseCase +}