Adiciona criacao de video
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -57,3 +57,5 @@ pids
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
/generated/prisma
|
||||
|
||||
/generated/prisma
|
||||
|
||||
@@ -44,8 +44,7 @@ model videos {
|
||||
situation video_situation
|
||||
error_message String? @db.VarChar(244)
|
||||
clips_quantity Int?
|
||||
times Json?
|
||||
duration Unsupported("interval")?
|
||||
duration String? @db.VarChar(16)
|
||||
title String? @db.VarChar(244)
|
||||
filename String? @db.VarChar(244)
|
||||
videoid String? @db.VarChar(244)
|
||||
|
||||
@@ -9,9 +9,6 @@ import { VideosController } from './modules/videos/videos.controller';
|
||||
import { UsuariosModule } from './modules/usuarios/usuarios.module';
|
||||
import { LoggerMiddleware } from './middleware/logger.middleware';
|
||||
import { RolesGuard } from './modules/auth/roles.guard';
|
||||
import { OllamaModule } from './modules/ollama/ollama.module';
|
||||
import { GeminiService } from './gemini/gemini.service';
|
||||
import { GeminiModule } from './gemini/gemini.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -20,11 +17,9 @@ import { GeminiModule } from './gemini/gemini.module';
|
||||
VideosModule,
|
||||
AuthModule,
|
||||
UsuariosModule,
|
||||
OllamaModule,
|
||||
GeminiModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, RolesGuard, GeminiService],
|
||||
providers: [AppService, RolesGuard],
|
||||
})
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import axios, { isAxiosError } from 'axios';
|
||||
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
|
||||
@@ -42,10 +46,10 @@ export class AuthService {
|
||||
} catch (error) {
|
||||
if (isAxiosError(error)) {
|
||||
console.error(error.response?.data);
|
||||
throw new UnauthorizedException(error.response?.data);
|
||||
throw new BadRequestException(error.response?.data);
|
||||
}
|
||||
|
||||
throw new UnauthorizedException('Usuário ou senha inválidos');
|
||||
throw new BadRequestException('Usuário ou senha inválidos');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
|
||||
import { Transform } from 'class-transformer';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsNumber,
|
||||
@@ -11,8 +8,6 @@ import {
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
export class CreateVideoDto {
|
||||
@IsOptional()
|
||||
@IsString({ message: 'Id deve ser uma string' })
|
||||
@@ -46,34 +41,29 @@ export class CreateVideoDto {
|
||||
@MaxLength(244)
|
||||
videoid?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: 'Duration deve ser uma string' })
|
||||
@Transform(({ value }: { value: number }) => {
|
||||
const duration = dayjs.duration(value, 'seconds');
|
||||
|
||||
return duration.format('HH:mm:ss');
|
||||
})
|
||||
duration?: string;
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
duration!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(244)
|
||||
description?: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(244)
|
||||
qualidade: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
@MaxLength(244)
|
||||
transcriptionUrl?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber(
|
||||
{ allowNaN: false, allowInfinity: false },
|
||||
{ message: 'Timestamp deve ser um número' },
|
||||
)
|
||||
@Transform(({ value }: { value: number }) => {
|
||||
const duration = dayjs(value * 1000);
|
||||
|
||||
return duration.format('DD/MM/YYYY HH:mm:ss');
|
||||
})
|
||||
timestamp?: string;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Query,
|
||||
Patch,
|
||||
Body,
|
||||
Delete,
|
||||
UseGuards,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
UsePipes,
|
||||
ValidationPipe,
|
||||
} from '@nestjs/common';
|
||||
import { Prisma, videos, video_situation } from 'generated/prisma';
|
||||
|
||||
@@ -57,6 +59,13 @@ export class VideosController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UsePipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
)
|
||||
@Roles('user', 'admin')
|
||||
async create(@Body() body: CreateVideoDto): Promise<void> {
|
||||
await this.videosService.createNewVideo(body);
|
||||
|
||||
@@ -12,6 +12,24 @@ import { PaginatedResponse } from '@shared/dto/paginated';
|
||||
import { ListVideosQueryDto } from './dto/list-videos-query.dto';
|
||||
import { VideoMetadataDto } from '@shared/dto/video-metadata';
|
||||
import { CreateVideoDto } from './dto/create-video-dto';
|
||||
import { N8N_REMOTE_SERVER } from '@/shared/url';
|
||||
|
||||
const formatSecondsToClock = (totalSeconds: number): string => {
|
||||
if (!Number.isFinite(totalSeconds)) {
|
||||
return '00:00:00';
|
||||
}
|
||||
|
||||
const safeSeconds = Math.max(0, Math.floor(totalSeconds));
|
||||
const hours = Math.floor(safeSeconds / 3600)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
const minutes = Math.floor((safeSeconds % 3600) / 60)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
const seconds = (safeSeconds % 60).toString().padStart(2, '0');
|
||||
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class VideosService {
|
||||
@@ -137,17 +155,62 @@ export class VideosService {
|
||||
}
|
||||
|
||||
async createNewVideo(data: CreateVideoDto): Promise<void> {
|
||||
const { url, videoid } = data;
|
||||
const { url, videoid, duration, qualidade, title, timestamp } = data;
|
||||
|
||||
const query: Prisma.videosWhereInput = { url };
|
||||
|
||||
if (videoid) {
|
||||
query.videoid = videoid;
|
||||
}
|
||||
|
||||
const searchUrl = await this.prisma.videos.findFirst({
|
||||
where: { url, videoid },
|
||||
where: query,
|
||||
});
|
||||
|
||||
if (!isEmpty(searchUrl)) {
|
||||
if (searchUrl && !isEmpty(searchUrl)) {
|
||||
throw new Error(
|
||||
`Esta video já foi renderizado, e se encontra na situação ${searchUrl.situation}`,
|
||||
);
|
||||
}
|
||||
|
||||
const formattedDuration = Number.isFinite(duration)
|
||||
? formatSecondsToClock(duration)
|
||||
: undefined;
|
||||
|
||||
const createData: Prisma.videosCreateInput = {
|
||||
url,
|
||||
situation: video_situation.FILA,
|
||||
};
|
||||
|
||||
if (videoid) {
|
||||
createData.videoid = videoid;
|
||||
}
|
||||
|
||||
if (title) {
|
||||
createData.title = title;
|
||||
}
|
||||
|
||||
if (formattedDuration) {
|
||||
createData.duration = formattedDuration;
|
||||
}
|
||||
|
||||
if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {
|
||||
createData.datetime_download = new Date(timestamp * 1000);
|
||||
}
|
||||
|
||||
await this.prisma.videos.create({
|
||||
data: createData,
|
||||
});
|
||||
|
||||
// const params = {
|
||||
// url,
|
||||
// videoid,
|
||||
// qualidade: qualidade === 'automatic' ? 'medium' : qualidade,
|
||||
// };
|
||||
|
||||
// await axios.get(`${N8N_REMOTE_SERVER}/webhook/download-video`, {
|
||||
// params,
|
||||
// });
|
||||
}
|
||||
|
||||
async update(id: number, data: Prisma.videosUpdateInput): Promise<videos> {
|
||||
|
||||
1
src/shared/url.ts
Normal file
1
src/shared/url.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const N8N_REMOTE_SERVER = 'https://becoming-poetic-lamb.ngrok-free.app';
|
||||
Reference in New Issue
Block a user