Corrige paginacao e adiciona endpoint de busca e delecao

This commit is contained in:
LeoMortari
2025-09-27 18:39:17 -03:00
parent 33f12b4f83
commit 76b7670fba
5 changed files with 193 additions and 32 deletions

2
.gitignore vendored
View File

@@ -12,7 +12,7 @@ yarn-debug.log*
yarn-error.log*
lerna-debug.log*
yarn.lock
pnpm-lock.yaml
# OS
.DS_Store

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<T> = {
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<VideoMetadataDto> {
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<PaginatedResponse<VideoResponseDto> | 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<videos> {
// return this.videosService.delete(Number(id));
// }
@Delete(':id')
async delete(@Param('id') id: string): Promise<videos> {
return this.videosService.delete(Number(id));
}
}

View File

@@ -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<VideoResponseDto[]> {
async list({
situation,
title,
}: {
situation?: video_situation;
title?: string;
}): Promise<VideoResponseDto[]> {
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<PaginatedResponse<VideoResponseDto>> {
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<VideoMetadataDto> {
console.log(url);
try {
const { data } = await axios.get<VideoMetadataDto>(
`${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<videos> {
return this.prisma.videos.update({
where: { id },