Corrige paginacao e adiciona endpoint de busca e delecao
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,7 +12,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|||||||
54
src/shared/dto/video-metadata.ts
Normal file
54
src/shared/dto/video-metadata.ts
Normal 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;
|
||||||
|
}
|
||||||
40
src/videos/dto/list-videos-query.dto.ts
Normal file
40
src/videos/dto/list-videos-query.dto.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -2,44 +2,65 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Param,
|
Param,
|
||||||
|
Query,
|
||||||
Patch,
|
Patch,
|
||||||
Body,
|
Body,
|
||||||
Query,
|
Delete,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} 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 { VideosService } from './videos.service';
|
||||||
import { VideoResponseDto } from './dto/video-response.dto';
|
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 { 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')
|
@Controller('videos')
|
||||||
@UseGuards(KeycloakAuthGuard)
|
@UseGuards(KeycloakAuthGuard)
|
||||||
export class VideosController {
|
export class VideosController {
|
||||||
constructor(private readonly videosService: VideosService) {}
|
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()
|
@Get()
|
||||||
@Roles('user', 'admin')
|
@Roles('user', 'admin')
|
||||||
async list(
|
async list(
|
||||||
@Query() query: PaginatedQueryDto,
|
@Query() query: ListVideosQueryDto,
|
||||||
@Query('situation') situation?: video_situation,
|
|
||||||
@Query('pageable', new EBooleanPipe(true)) pageable: boolean = true,
|
|
||||||
): Promise<PaginatedResponse<VideoResponseDto> | VideoResponseDto[]> {
|
): Promise<PaginatedResponse<VideoResponseDto> | VideoResponseDto[]> {
|
||||||
const situacao = situation?.toLocaleUpperCase() as video_situation;
|
if (query.pageable || query.page || query.perPage) {
|
||||||
|
return this.videosService.listPaginated(query);
|
||||||
if (pageable || query.page || query.perPage) {
|
|
||||||
return this.videosService.listPaginated(
|
|
||||||
Number(query.page ?? 1),
|
|
||||||
Number(query.perPage ?? 10),
|
|
||||||
query.direction as 'asc' | 'desc',
|
|
||||||
situacao,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.videosService.list(situacao);
|
return this.videosService.list({
|
||||||
|
situation: query.situation,
|
||||||
|
title: query.title,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@@ -55,8 +76,8 @@ export class VideosController {
|
|||||||
return this.videosService.update(Number(id), body);
|
return this.videosService.update(Number(id), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Delete(':id')
|
@Delete(':id')
|
||||||
// async delete(@Param('id') id: string): Promise<videos> {
|
async delete(@Param('id') id: string): Promise<videos> {
|
||||||
// return this.videosService.delete(Number(id));
|
return this.videosService.delete(Number(id));
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import axios from 'axios';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { plainToInstance } from 'class-transformer';
|
import { plainToInstance } from 'class-transformer';
|
||||||
|
|
||||||
@@ -6,14 +7,29 @@ import { Prisma, videos, video_situation } from 'generated/prisma';
|
|||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { VideoResponseDto } from './dto/video-response.dto';
|
import { VideoResponseDto } from './dto/video-response.dto';
|
||||||
import { PaginatedResponse } from '../shared/dto/paginated';
|
import { PaginatedResponse } from '../shared/dto/paginated';
|
||||||
|
import { ListVideosQueryDto } from './dto/list-videos-query.dto';
|
||||||
|
import { VideoMetadataDto } from 'src/shared/dto/video-metadata';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VideosService {
|
export class VideosService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
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({
|
const data = await this.prisma.videos.findMany({
|
||||||
where: situation ? { situation } : {},
|
where,
|
||||||
orderBy: { id: 'desc' },
|
orderBy: { id: 'desc' },
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -33,20 +49,29 @@ export class VideosService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async listPaginated(
|
async listPaginated(
|
||||||
page: number,
|
query: ListVideosQueryDto,
|
||||||
perPage: number,
|
|
||||||
direction: 'asc' | 'desc' = 'desc',
|
|
||||||
situation?: video_situation,
|
|
||||||
): Promise<PaginatedResponse<VideoResponseDto>> {
|
): Promise<PaginatedResponse<VideoResponseDto>> {
|
||||||
const skip = page >= 1 ? page * perPage : 0;
|
const page = Number(query.page ?? 1);
|
||||||
const where = situation ? { situation } : {};
|
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([
|
const [rows, total] = await Promise.all([
|
||||||
this.prisma.videos.findMany({
|
this.prisma.videos.findMany({
|
||||||
where,
|
where,
|
||||||
orderBy: { id: direction },
|
orderBy: { id: direction },
|
||||||
skip,
|
skip,
|
||||||
take: perPage ?? 1,
|
take: perPage,
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
title: 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> {
|
async update(id: number, data: Prisma.videosUpdateInput): Promise<videos> {
|
||||||
return this.prisma.videos.update({
|
return this.prisma.videos.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|||||||
Reference in New Issue
Block a user