Ajusta endpoints

This commit is contained in:
LeoMortari
2025-08-21 17:47:05 -03:00
parent 44a1625631
commit 2ed4a9122d
9 changed files with 131 additions and 69 deletions

1
.gitignore vendored
View File

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

View File

@@ -14,7 +14,7 @@ model videos {
datetime_download DateTime? @db.Timestamp(6) datetime_download DateTime? @db.Timestamp(6)
datetime_convert DateTime? @db.Timestamp(6) datetime_convert DateTime? @db.Timestamp(6)
url String @db.VarChar(244) url String @db.VarChar(244)
situation EVideoSituation situation video_situation
error_message String? @db.VarChar(244) error_message String? @db.VarChar(244)
clips_quantity Int? clips_quantity Int?
times Json? times Json?
@@ -25,7 +25,7 @@ model videos {
datetime_posted DateTime? @db.Timestamp(6) datetime_posted DateTime? @db.Timestamp(6)
} }
enum EVideoSituation { enum video_situation {
FILA FILA
PROCESSANDO PROCESSANDO
ERRO ERRO

View File

@@ -1,8 +1,13 @@
import { NestFactory } from '@nestjs/core'; import { ClassSerializerInterceptor } from '@nestjs/common';
import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
const reflector = app.get(Reflector);
app.useGlobalInterceptors(new ClassSerializerInterceptor(reflector));
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }
bootstrap(); void bootstrap();

View File

@@ -7,7 +7,7 @@ import {
IsUrl, IsUrl,
MaxLength, MaxLength,
} from 'class-validator'; } from 'class-validator';
import { EVideoSituation } from '../../../generated/prisma'; import { video_situation } from '../../../generated/prisma';
export class CreateVideoDto { export class CreateVideoDto {
@IsUrl() @IsUrl()
@@ -15,8 +15,8 @@ export class CreateVideoDto {
url!: string; url!: string;
@IsOptional() @IsOptional()
@IsEnum(EVideoSituation) @IsEnum(video_situation)
situation?: EVideoSituation; situation?: video_situation;
@IsOptional() @IsOptional()
@IsString() @IsString()

View File

@@ -1,5 +1,5 @@
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsInt, IsOptional, Max, Min } from 'class-validator'; import { IsIn, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
export class PaginatedQueryDto { export class PaginatedQueryDto {
@IsOptional() @IsOptional()
@@ -14,12 +14,19 @@ export class PaginatedQueryDto {
@Min(1) @Min(1)
@Max(100) @Max(100)
perPage: number = 20; perPage: number = 20;
@IsOptional()
@Type(() => String)
@IsString()
@IsIn(['asc', 'desc'])
direction: string = 'desc';
} }
export type PaginatedResponse<T> = { export type PaginatedResponse<T> = {
content: T[]; content: T[];
pagination: { pagination: {
page: number; page: number;
direction: string;
perPage: number; perPage: number;
total: number; total: number;
totalPages: number; totalPages: number;

View File

@@ -1,12 +1,41 @@
import { EVideoSituation } from 'generated/prisma'; import { Expose, Transform, TransformFnParams } from 'class-transformer';
import dayjs from 'dayjs';
import { video_situation } from 'generated/prisma';
export interface VideoResponseDto { export class VideoResponseDto {
id: number; @Expose()
title: string | null; id!: number;
url: string;
situation: EVideoSituation; @Expose()
clips_quantity: number; title!: string | null;
videoid: string;
filename: string; @Expose()
datetime_download: Date | string; url!: string;
@Expose()
situation!: video_situation;
@Expose()
@Transform(({ value }: TransformFnParams) =>
typeof value === 'number' ? value : 0,
)
clips_quantity!: number;
@Expose()
@Transform(({ value }: TransformFnParams) =>
typeof value === 'string' ? value : '',
)
videoid!: string;
@Expose()
@Transform(({ value }: TransformFnParams) =>
typeof value === 'string' ? value : '',
)
filename!: string;
@Expose()
@Transform(({ value }: TransformFnParams) =>
value ? dayjs(value as Date | string).format('DD/MM/YYYY HH:mm:ss') : '',
)
datetime_download!: string;
} }

View File

@@ -7,11 +7,12 @@ import {
Body, Body,
Query, Query,
} from '@nestjs/common'; } from '@nestjs/common';
import { videos, Prisma, EVideoSituation } from 'generated/prisma'; import { videos, Prisma, 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 './dto/paginated.dto'; import { PaginatedQueryDto, PaginatedResponse } from './dto/paginated.dto';
import { EBooleanPipe } from './videos.pipe';
@Controller('videos') @Controller('videos')
export class VideosController { export class VideosController {
@@ -20,13 +21,21 @@ export class VideosController {
@Get() @Get()
async list( async list(
@Query() query: PaginatedQueryDto, @Query() query: PaginatedQueryDto,
@Query('situation') situation?: EVideoSituation, @Query('situation') situation?: video_situation,
): Promise<PaginatedResponse<VideoResponseDto>> { @Query('pageable', new EBooleanPipe(true)) pageable: boolean = true,
return this.videosService.listPaginated( ): Promise<PaginatedResponse<VideoResponseDto> | VideoResponseDto[]> {
query.page, const situacao = situation?.toLocaleUpperCase() as video_situation;
query.perPage,
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,
);
}
return this.videosService.list(situacao);
} }
@Get(':id') @Get(':id')
@@ -46,9 +55,4 @@ export class VideosController {
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));
} }
@Get('situation/:s')
async listBySituation(@Param('s') s: EVideoSituation): Promise<videos[]> {
return this.videosService.listBySituation(s);
}
} }

22
src/videos/videos.pipe.ts Normal file
View File

@@ -0,0 +1,22 @@
import { Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
@Injectable()
export class EBooleanPipe
implements PipeTransform<string | undefined, boolean>
{
constructor(private readonly defaultValue?: boolean) {}
transform(value: string | undefined): boolean {
if (value === undefined || value === null || value === '') {
if (this.defaultValue !== undefined) return this.defaultValue;
throw new BadRequestException('Parâmetro esperado: "V" ou "F"');
}
const normalized = value.toString().trim().toUpperCase();
if (normalized === 'V') return true;
if (normalized === 'F') return false;
throw new BadRequestException('Valor inválido. Use "V" ou "F"');
}
}

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import dayjs from 'dayjs'; import { plainToInstance } from 'class-transformer';
import { Prisma, videos, EVideoSituation } from 'generated/prisma'; 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';
@@ -11,37 +11,42 @@ import { PaginatedResponse } from './dto/paginated.dto';
export class VideosService { export class VideosService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
async list(): Promise<VideoResponseDto[]> { async list(situation?: video_situation): Promise<VideoResponseDto[]> {
const data = await this.prisma.videos.findMany({ orderBy: { id: 'desc' } }); const data = await this.prisma.videos.findMany({
where: situation ? { situation } : {},
orderBy: { id: 'desc' },
select: {
id: true,
title: true,
url: true,
situation: true,
clips_quantity: true,
videoid: true,
filename: true,
datetime_download: true,
},
});
return data.map((video) => ({ return plainToInstance(VideoResponseDto, data, {
id: video.id, excludeExtraneousValues: true,
title: video.title, });
url: video.url,
situation: video.situation,
clips_quantity: video.clips_quantity ?? 0,
videoid: video.videoid!,
filename: video.filename || '',
datetime_download: video.datetime_download
? dayjs(video.datetime_download).format('DD/MM/YYYY HH:mm:ss')
: '',
}));
} }
async listPaginated( async listPaginated(
page: number, page: number,
perPage: number, perPage: number,
situation?: EVideoSituation, direction: 'asc' | 'desc' = 'desc',
situation?: video_situation,
): Promise<PaginatedResponse<VideoResponseDto>> { ): Promise<PaginatedResponse<VideoResponseDto>> {
const skip = (page - 1) * perPage; const skip = page >= 1 ? page * perPage : 0;
const where = situation ? { situation } : undefined; const where = situation ? { situation } : {};
const [rows, total] = await Promise.all([ const [rows, total] = await Promise.all([
this.prisma.videos.findMany({ this.prisma.videos.findMany({
where, where,
orderBy: { id: 'asc' }, orderBy: { id: direction },
skip, skip,
take: Number(perPage), take: perPage ?? 1,
select: { select: {
id: true, id: true,
title: true, title: true,
@@ -56,16 +61,11 @@ export class VideosService {
this.prisma.videos.count({ where }), this.prisma.videos.count({ where }),
]); ]);
const content: VideoResponseDto[] = rows.map((row) => ({ const content: VideoResponseDto[] = plainToInstance(
id: row.id, VideoResponseDto,
title: row.title ?? null, rows,
url: row.url, { excludeExtraneousValues: true },
situation: row.situation, );
clips_quantity: row.clips_quantity ?? 0,
videoid: row.videoid ?? '',
filename: row.filename ?? '',
datetime_download: row.datetime_download ?? '',
}));
const totalPages = Math.max(1, Math.ceil(total / perPage)); const totalPages = Math.max(1, Math.ceil(total / perPage));
@@ -73,6 +73,7 @@ export class VideosService {
content, content,
pagination: { pagination: {
page, page,
direction,
perPage, perPage,
total, total,
totalPages, totalPages,
@@ -100,11 +101,4 @@ export class VideosService {
where: { id }, where: { id },
}); });
} }
async listBySituation(situation: EVideoSituation): Promise<videos[]> {
return this.prisma.videos.findMany({
where: { situation },
orderBy: { id: 'desc' },
});
}
} }