From e7e76d15e27b7a7865165b236e606713572c0d96 Mon Sep 17 00:00:00 2001 From: LeoMortari Date: Tue, 4 Nov 2025 20:03:16 -0300 Subject: [PATCH] Ajusta CRLF e add files --- .env.example | 5 +++ package.json | 1 + src/app.module.ts | 4 +++ src/minio/minio.module.ts | 11 ++++++ src/minio/minio.service.ts | 36 ++++++++++++++++++++ src/modules/files/files.controller.ts | 29 ++++++++++++++++ src/modules/files/files.module.ts | 9 +++++ src/modules/files/files.service.ts | 49 +++++++++++++++++++++++++++ 8 files changed, 144 insertions(+) create mode 100644 src/minio/minio.module.ts create mode 100644 src/minio/minio.service.ts create mode 100644 src/modules/files/files.controller.ts create mode 100644 src/modules/files/files.module.ts create mode 100644 src/modules/files/files.service.ts diff --git a/.env.example b/.env.example index 8096b32..73c0316 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,8 @@ PORT=3000 DATABASE_URL="postgresql://username:password@postgres:5432/clipperia?schema=public" KEYCLOAK_URL="https://auth.clipperia.com.br" YOUTUBE_API_URL="https://your-youtube-api-url.com" +MINIO_ENDPOINT="minio.clipperia.com.br" +MINIO_PORT=9000 +MINIO_USE_SSL=true +MINIO_ACCESS_KEY="user" +MINIO_SECRET_KEY="password" diff --git a/package.json b/package.json index 7a0f4ac..8d18b6a 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "dayjs": "1.11.13", "jwks-rsa": "3.2.0", "lodash": "4.17.21", + "minio": "8.0.6", "passport": "0.7.0", "passport-jwt": "4.0.1" }, diff --git a/src/app.module.ts b/src/app.module.ts index c5becc4..ca10c16 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,12 +1,14 @@ import { Module, MiddlewareConsumer } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { PrismaModule } from './prisma/prisma.module'; +import { MinioModule } from './minio/minio.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { VideosModule } from './modules/videos/videos.module'; import { AuthModule } from './modules/auth/auth.module'; import { VideosController } from './modules/videos/videos.controller'; import { UsuariosModule } from './modules/usuarios/usuarios.module'; +import { FilesModule } from './modules/files/files.module'; import { LoggerMiddleware } from './middleware/logger.middleware'; import { RolesGuard } from './modules/auth/roles.guard'; @@ -14,9 +16,11 @@ import { RolesGuard } from './modules/auth/roles.guard'; imports: [ ConfigModule.forRoot(), PrismaModule, + MinioModule, VideosModule, AuthModule, UsuariosModule, + FilesModule, ], controllers: [AppController], providers: [AppService, RolesGuard], diff --git a/src/minio/minio.module.ts b/src/minio/minio.module.ts new file mode 100644 index 0000000..2bb45ca --- /dev/null +++ b/src/minio/minio.module.ts @@ -0,0 +1,11 @@ +import { Module, Global } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { MinioService } from './minio.service'; + +@Global() +@Module({ + imports: [ConfigModule], + providers: [MinioService], + exports: [MinioService], +}) +export class MinioModule {} diff --git a/src/minio/minio.service.ts b/src/minio/minio.service.ts new file mode 100644 index 0000000..269b954 --- /dev/null +++ b/src/minio/minio.service.ts @@ -0,0 +1,36 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as Minio from 'minio'; + +@Injectable() +export class MinioService implements OnModuleInit, OnModuleDestroy { + private client: Minio.Client; + + constructor(private configService: ConfigService) {} + + onModuleInit() { + const endpoint = this.configService.get( + 'MINIO_ENDPOINT', + 'https://minio.clipperia.com.br', + ); + + const cleanEndpoint = endpoint.replace(/^https?:\/\//, ''); + + this.client = new Minio.Client({ + endPoint: cleanEndpoint, + port: parseInt(this.configService.get('MINIO_PORT', '443')), + useSSL: + this.configService.get('MINIO_USE_SSL', 'true') === 'true', + accessKey: this.configService.get('MINIO_ACCESS_KEY'), + secretKey: this.configService.get('MINIO_SECRET_KEY'), + }); + } + + async onModuleDestroy(): Promise { + // MinIO client doesn't require explicit disconnect + } + + getClient(): Minio.Client { + return this.client; + } +} diff --git a/src/modules/files/files.controller.ts b/src/modules/files/files.controller.ts new file mode 100644 index 0000000..e4ddbd5 --- /dev/null +++ b/src/modules/files/files.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { FilesService, type FileInfo } from './files.service'; +import { RolesGuard } from '../auth/roles.guard'; +import { Roles } from '../auth/decorator/roles.decorator'; + +@Controller('files') +@UseGuards(AuthGuard('jwt'), RolesGuard) +export class FilesController { + constructor(private readonly filesService: FilesService) {} + + @Get('icons') + @Roles('admin') + async listIcons(): Promise<{ + bucket: string; + folder: string; + files: FileInfo[]; + total: number; + }> { + const files = await this.filesService.listIconsFromBucket(); + + return { + bucket: 'clipperia', + folder: 'icons', + files, + total: files.length, + }; + } +} diff --git a/src/modules/files/files.module.ts b/src/modules/files/files.module.ts new file mode 100644 index 0000000..bcfb777 --- /dev/null +++ b/src/modules/files/files.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { FilesController } from './files.controller'; +import { FilesService } from './files.service'; + +@Module({ + controllers: [FilesController], + providers: [FilesService], +}) +export class FilesModule {} diff --git a/src/modules/files/files.service.ts b/src/modules/files/files.service.ts new file mode 100644 index 0000000..39858e1 --- /dev/null +++ b/src/modules/files/files.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { MinioService } from '../../minio/minio.service'; + +export interface FileInfo { + name: string; + size: number; + lastModified: Date; + etag: string; +} + +@Injectable() +export class FilesService { + constructor(private readonly minioService: MinioService) {} + + async listIconsFromBucket(): Promise { + const client = this.minioService.getClient(); + const bucketName = 'clipperia'; + const prefix = 'icons/'; + + return new Promise((resolve, reject) => { + const files: FileInfo[] = []; + const objectsStream = client.listObjects(bucketName, prefix, true); + + objectsStream.on('data', (obj) => { + console.log(obj); + + if (obj.name) { + files.push({ + name: obj.name, + size: obj.size!, + lastModified: obj.lastModified!, + etag: obj.etag!, + }); + } + }); + + objectsStream.on('error', (err) => { + console.log(err); + + reject(err); + }); + + objectsStream.on('end', () => { + console.log('ok'); + resolve(files); + }); + }); + } +}