Ajusta endpoints
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
22
src/videos/videos.pipe.ts
Normal 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"');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user