From 76b7670fbaa9d7de0b9db02a75853dd74fe79afb Mon Sep 17 00:00:00 2001 From: LeoMortari Date: Sat, 27 Sep 2025 18:39:17 -0300 Subject: [PATCH] Corrige paginacao e adiciona endpoint de busca e delecao --- .gitignore | 2 +- src/shared/dto/video-metadata.ts | 54 ++++++++++++++++++++ src/videos/dto/list-videos-query.dto.ts | 40 +++++++++++++++ src/videos/videos.controller.ts | 65 ++++++++++++++++--------- src/videos/videos.service.ts | 64 ++++++++++++++++++++---- 5 files changed, 193 insertions(+), 32 deletions(-) create mode 100644 src/shared/dto/video-metadata.ts create mode 100644 src/videos/dto/list-videos-query.dto.ts diff --git a/.gitignore b/.gitignore index 7f2ffd5..6adf6fc 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ yarn-debug.log* yarn-error.log* lerna-debug.log* yarn.lock - +pnpm-lock.yaml # OS .DS_Store diff --git a/src/shared/dto/video-metadata.ts b/src/shared/dto/video-metadata.ts new file mode 100644 index 0000000..7080980 --- /dev/null +++ b/src/shared/dto/video-metadata.ts @@ -0,0 +1,54 @@ +import { Expose } from 'class-transformer'; + +export class VideoMetadataDto { + @Expose() + id: string; + + @Expose() + title: string; + + @Expose() + thumbnail: string; + + @Expose() + description: string; + + @Expose() + channel_id: string; + + @Expose() + channel_url: string; + + @Expose() + duration: number; + + @Expose() + view_count: number; + + @Expose() + webpage_url: string; + + @Expose() + categories: string[]; + + @Expose() + tags: string[]; + + @Expose() + comment_count: number; + + @Expose() + like_count: number; + + @Expose() + channel: string; + + @Expose() + channel_follower_count: number; + + @Expose() + uploader: string; + + @Expose() + timestamp: number; +} diff --git a/src/videos/dto/list-videos-query.dto.ts b/src/videos/dto/list-videos-query.dto.ts new file mode 100644 index 0000000..9eb498c --- /dev/null +++ b/src/videos/dto/list-videos-query.dto.ts @@ -0,0 +1,40 @@ +import { + IsEnum, + IsOptional, + IsString, + IsBoolean, + IsNumber, +} from 'class-validator'; +import { video_situation } from 'generated/prisma'; +import { Transform } from 'class-transformer'; + +export class ListVideosQueryDto { + @IsEnum(video_situation) + @IsOptional() + @Transform( + ({ value }: { value: string }) => value?.toUpperCase() as video_situation, + ) + situation?: video_situation; + + @IsString() + @IsOptional() + title?: string; + + @IsNumber() + @IsOptional() + @Transform(({ value }) => (value ? Number(value) : 1)) + page?: number; + + @IsNumber() + @IsOptional() + @Transform(({ value }) => (value ? Number(value) : 10)) + perPage?: number = 10; + + @IsOptional() + direction: 'asc' | 'desc' = 'desc'; + + @Transform(({ value }) => value !== 'false') + @IsBoolean() + @IsOptional() + pageable: boolean = true; +} diff --git a/src/videos/videos.controller.ts b/src/videos/videos.controller.ts index 43b4a9a..0241d7b 100644 --- a/src/videos/videos.controller.ts +++ b/src/videos/videos.controller.ts @@ -2,44 +2,65 @@ import { Controller, Get, Param, + Query, Patch, Body, - Query, + Delete, UseGuards, } from '@nestjs/common'; -import { videos, Prisma, video_situation } from 'generated/prisma'; +import { Prisma, videos, video_situation } from 'generated/prisma'; import { VideosService } from './videos.service'; import { VideoResponseDto } from './dto/video-response.dto'; -import { PaginatedQueryDto, PaginatedResponse } from '../shared/dto/paginated'; -import { EBooleanPipe } from '../shared/pipe'; import { KeycloakAuthGuard } from '../auth/keycloak-auth.guard'; -import { Roles } from 'src/auth/decorator/roles.decorator'; +import { Roles } from '../auth/decorator/roles.decorator'; +import { ListVideosQueryDto } from './dto/list-videos-query.dto'; +import { VideoMetadataDto } from 'src/shared/dto/video-metadata'; + +type PaginatedResponse = { + content: T[]; + pagination: { + page: number; + perPage: number; + total: number; + totalPages: number; + hasNext: boolean; + hasPrev: boolean; + }; +}; @Controller('videos') @UseGuards(KeycloakAuthGuard) export class VideosController { constructor(private readonly videosService: VideosService) {} + @Get('situacoes') + @Roles('user', 'admin') + getSituacao(): video_situation[] { + return Object.values(video_situation) as video_situation[]; + } + + @Get('search') + @Roles('user', 'admin') + getVideoMetadata( + @Query() { url }: { url: string }, + ): Promise { + return this.videosService.getVideoMetadata(url); + } + @Get() @Roles('user', 'admin') async list( - @Query() query: PaginatedQueryDto, - @Query('situation') situation?: video_situation, - @Query('pageable', new EBooleanPipe(true)) pageable: boolean = true, + @Query() query: ListVideosQueryDto, ): Promise | VideoResponseDto[]> { - const situacao = situation?.toLocaleUpperCase() as video_situation; - - if (pageable || query.page || query.perPage) { - return this.videosService.listPaginated( - Number(query.page ?? 1), - Number(query.perPage ?? 10), - query.direction as 'asc' | 'desc', - situacao, - ); + if (query.pageable || query.page || query.perPage) { + return this.videosService.listPaginated(query); } - return this.videosService.list(situacao); + return this.videosService.list({ + situation: query.situation, + title: query.title, + }); } @Get(':id') @@ -55,8 +76,8 @@ export class VideosController { return this.videosService.update(Number(id), body); } - // @Delete(':id') - // async delete(@Param('id') id: string): Promise { - // return this.videosService.delete(Number(id)); - // } + @Delete(':id') + async delete(@Param('id') id: string): Promise { + return this.videosService.delete(Number(id)); + } } diff --git a/src/videos/videos.service.ts b/src/videos/videos.service.ts index 7c34cf2..d9aaee9 100644 --- a/src/videos/videos.service.ts +++ b/src/videos/videos.service.ts @@ -1,3 +1,4 @@ +import axios from 'axios'; import { Injectable } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; @@ -6,14 +7,29 @@ import { Prisma, videos, video_situation } from 'generated/prisma'; import { PrismaService } from '../prisma/prisma.service'; import { VideoResponseDto } from './dto/video-response.dto'; import { PaginatedResponse } from '../shared/dto/paginated'; +import { ListVideosQueryDto } from './dto/list-videos-query.dto'; +import { VideoMetadataDto } from 'src/shared/dto/video-metadata'; @Injectable() export class VideosService { constructor(private readonly prisma: PrismaService) {} - async list(situation?: video_situation): Promise { + async list({ + situation, + title, + }: { + situation?: video_situation; + title?: string; + }): Promise { + const where: Prisma.videosWhereInput = situation ? { situation } : {}; + + if (title) { + where.title = { + contains: title, + }; + } const data = await this.prisma.videos.findMany({ - where: situation ? { situation } : {}, + where, orderBy: { id: 'desc' }, select: { id: true, @@ -33,20 +49,29 @@ export class VideosService { } async listPaginated( - page: number, - perPage: number, - direction: 'asc' | 'desc' = 'desc', - situation?: video_situation, + query: ListVideosQueryDto, ): Promise> { - const skip = page >= 1 ? page * perPage : 0; - const where = situation ? { situation } : {}; + const page = Number(query.page ?? 1); + const perPage = Number(query.perPage ?? 1); + const direction = query.direction ?? 'desc'; + + const skip = page > 0 ? (page - 1) * perPage : 0; + const where: Prisma.videosWhereInput = query.situation + ? { situation: query.situation } + : {}; + + if (query.title) { + where.title = { + contains: query.title, + }; + } const [rows, total] = await Promise.all([ this.prisma.videos.findMany({ where, orderBy: { id: direction }, skip, - take: perPage ?? 1, + take: perPage, select: { id: true, title: true, @@ -89,6 +114,27 @@ export class VideosService { }); } + async getVideoMetadata(url: string): Promise { + console.log(url); + try { + const { data } = await axios.get( + `${process.env.YOUTUBE_API_URL}/get-video-metadata`, + { + params: { + url, + }, + }, + ); + + return plainToInstance(VideoMetadataDto, data, { + excludeExtraneousValues: true, + }); + } catch (error) { + console.log(error); + throw new Error('Erro ao obter metadados do vĂ­deo'); + } + } + async update(id: number, data: Prisma.videosUpdateInput): Promise { return this.prisma.videos.update({ where: { id },