Adiciona controller do Ollama e reestrutura arquitetura
This commit is contained in:
59
src/modules/videos/dto/create-video-dto.ts
Normal file
59
src/modules/videos/dto/create-video-dto.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsISO8601,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
import { video_situation } from '@root/generated/prisma';
|
||||
|
||||
export class CreateVideoDto {
|
||||
@IsUrl()
|
||||
@MaxLength(244)
|
||||
url!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(video_situation)
|
||||
situation?: video_situation;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(244)
|
||||
error_message?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
clips_quantity?: number;
|
||||
|
||||
@IsOptional()
|
||||
times?: unknown;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(244)
|
||||
title?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(244)
|
||||
filename?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(244)
|
||||
videoid?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
datetime_download?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
datetime_convert?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
datetime_posted?: string;
|
||||
}
|
||||
40
src/modules/videos/dto/list-videos-query.dto.ts
Normal file
40
src/modules/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;
|
||||
}
|
||||
41
src/modules/videos/dto/video-response.dto.ts
Normal file
41
src/modules/videos/dto/video-response.dto.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Expose, Transform, TransformFnParams } from 'class-transformer';
|
||||
import dayjs from 'dayjs';
|
||||
import { video_situation } from 'generated/prisma';
|
||||
|
||||
export class VideoResponseDto {
|
||||
@Expose()
|
||||
id!: number;
|
||||
|
||||
@Expose()
|
||||
title!: string | null;
|
||||
|
||||
@Expose()
|
||||
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;
|
||||
}
|
||||
83
src/modules/videos/videos.controller.ts
Normal file
83
src/modules/videos/videos.controller.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Query,
|
||||
Patch,
|
||||
Body,
|
||||
Delete,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { Prisma, videos, video_situation } from 'generated/prisma';
|
||||
|
||||
import { VideosService } from './videos.service';
|
||||
import { VideoResponseDto } from './dto/video-response.dto';
|
||||
import { KeycloakAuthGuard } from '../auth/keycloak-auth.guard';
|
||||
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: ListVideosQueryDto,
|
||||
): Promise<PaginatedResponse<VideoResponseDto> | VideoResponseDto[]> {
|
||||
if (query.pageable || query.page || query.perPage) {
|
||||
return this.videosService.listPaginated(query);
|
||||
}
|
||||
|
||||
return this.videosService.list({
|
||||
situation: query.situation,
|
||||
title: query.title,
|
||||
});
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async get(@Param('id') id: string): Promise<videos | null> {
|
||||
return this.videosService.get(Number(id));
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() body: Prisma.videosUpdateInput,
|
||||
): Promise<videos> {
|
||||
return this.videosService.update(Number(id), body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@Param('id') id: string): Promise<videos> {
|
||||
return this.videosService.delete(Number(id));
|
||||
}
|
||||
}
|
||||
12
src/modules/videos/videos.module.ts
Normal file
12
src/modules/videos/videos.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { VideosService } from './videos.service';
|
||||
import { VideosController } from './videos.controller';
|
||||
import { PrismaModule } from '@prisma/prisma.module';
|
||||
import { KeycloakAuthGuard } from '@modules/auth/keycloak-auth.guard';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [VideosService, KeycloakAuthGuard],
|
||||
controllers: [VideosController],
|
||||
})
|
||||
export class VideosModule {}
|
||||
148
src/modules/videos/videos.service.ts
Normal file
148
src/modules/videos/videos.service.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import axios from 'axios';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
|
||||
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 '@shared/dto/video-metadata';
|
||||
|
||||
@Injectable()
|
||||
export class VideosService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
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,
|
||||
orderBy: { id: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
url: true,
|
||||
situation: true,
|
||||
clips_quantity: true,
|
||||
videoid: true,
|
||||
filename: true,
|
||||
datetime_download: true,
|
||||
},
|
||||
});
|
||||
|
||||
return plainToInstance(VideoResponseDto, data, {
|
||||
excludeExtraneousValues: true,
|
||||
});
|
||||
}
|
||||
|
||||
async listPaginated(
|
||||
query: ListVideosQueryDto,
|
||||
): Promise<PaginatedResponse<VideoResponseDto>> {
|
||||
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,
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
url: true,
|
||||
situation: true,
|
||||
clips_quantity: true,
|
||||
videoid: true,
|
||||
filename: true,
|
||||
datetime_download: true,
|
||||
},
|
||||
}),
|
||||
this.prisma.videos.count({ where }),
|
||||
]);
|
||||
|
||||
const content: VideoResponseDto[] = plainToInstance(
|
||||
VideoResponseDto,
|
||||
rows,
|
||||
{ excludeExtraneousValues: true },
|
||||
);
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
||||
|
||||
return {
|
||||
content,
|
||||
pagination: {
|
||||
page,
|
||||
direction,
|
||||
perPage,
|
||||
total,
|
||||
totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async get(id: number): Promise<videos | null> {
|
||||
return this.prisma.videos.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async getVideoMetadata(url: string): Promise<VideoMetadataDto> {
|
||||
try {
|
||||
const { data } = await axios.get<VideoMetadataDto>(
|
||||
`${process.env.YOUTUBE_API_URL}/get-video-metadata`,
|
||||
{
|
||||
params: {
|
||||
url,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return plainToInstance(VideoMetadataDto, data, {
|
||||
excludeExtraneousValues: true,
|
||||
});
|
||||
} catch {
|
||||
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 },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<videos> {
|
||||
return this.prisma.videos.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user