diff --git a/.env.example b/.env.example index ec06da4..827f0e5 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ NODE_ENV=development PORT=3000 # Database -DATABASE_URL="postgresql://postgres:postgres@localhost:5432/boilerplate_db?schema=public" +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/contentgen_db?schema=public" # JWT JWT_SECRET=your-super-secret-jwt-key-change-in-production @@ -16,46 +16,85 @@ REDIS_PORT=6379 REDIS_PASSWORD= # i18n -DEFAULT_LANGUAGE=en +DEFAULT_LANGUAGE=tr FALLBACK_LANGUAGE=en +# Frontend URL (CORS ve WebSocket için) +FRONTEND_URL=http://localhost:3001 + # Optional Features (set to "true" to enable) ENABLE_MAIL=false -ENABLE_S3=false -ENABLE_WEBSOCKET=false +ENABLE_S3=true +ENABLE_WEBSOCKET=true ENABLE_MULTI_TENANCY=false +ENABLE_GEMINI=true # Mail (Optional - only needed if ENABLE_MAIL=true) MAIL_HOST=smtp.example.com MAIL_PORT=587 MAIL_USER= MAIL_PASSWORD= -MAIL_FROM=noreply@example.com +MAIL_FROM=noreply@contentgen.ai -# S3/MinIO (Optional - only needed if ENABLE_S3=true) -S3_ENDPOINT=http://localhost:9000 -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -S3_BUCKET=uploads -S3_REGION=us-east-1 +# Cloudflare R2 / S3 Storage (ENABLE_S3=true gerekir) +# Cloudflare R2: https://dash.cloudflare.com → R2 → API Tokens +S3_ENDPOINT=https://.r2.cloudflarestorage.com +S3_ACCESS_KEY=your-r2-access-key +S3_SECRET_KEY=your-r2-secret-key +S3_BUCKET=contentgen-media +S3_REGION=auto +S3_PUBLIC_URL=https://pub-xxxx.r2.dev # Throttle / Rate Limiting THROTTLE_TTL=60000 THROTTLE_LIMIT=100 -# Gemini AI (Optional - only needed if ENABLE_GEMINI=true) -ENABLE_GEMINI=false -GOOGLE_API_KEY=your-google-api-key +# ─── AI API Keys ──────────────────────────────────────────────────── + +# Google Gemini AI — Script üretimi (ZORUNLU) +# https://aistudio.google.com/apikey +GOOGLE_API_KEY=your-google-gemini-api-key GEMINI_MODEL=gemini-2.5-flash -# AudioCraft — HuggingFace Inference API (MusicGen + AudioGen) -# MusicGen: Text-to-music üretimi -# AudioGen: Text-to-sound efekti üretimi -# Ücretsiz HuggingFace hesabı yeterli: https://huggingface.co/settings/tokens +# HiggsField AI — Video clip üretimi (ZORUNLU) +# https://higgsfield.ai → Dashboard → API Keys +HIGGSFIELD_API_KEY=your-higgsfield-api-key +HIGGSFIELD_BASE_URL=https://api.higgsfield.ai/v1 + +# ElevenLabs — Text-to-Speech narrasyon (ZORUNLU) +# https://elevenlabs.io → Profile → API Key +ELEVENLABS_API_KEY=your-elevenlabs-api-key +ELEVENLABS_VOICE_ID=21m00Tcm4TlvDq8ikWAM +ELEVENLABS_MODEL_ID=eleven_multilingual_v2 + +# Suno AI — Müzik üretimi (ZORUNLU) +# https://suno.com → API erişimi için iletişime geçin +SUNO_API_KEY=your-suno-api-key +SUNO_BASE_URL=https://api.suno.ai/v1 + +# AudioCraft — HuggingFace (Ambient ses, opsiyonel) +# https://huggingface.co/settings/tokens HUGGINGFACE_API_KEY=hf_your-huggingface-api-key MUSICGEN_MODEL=facebook/musicgen-small AUDIOGEN_MODEL=facebook/audiogen-medium -# Stripe Billing -STRIPE_SECRET_KEY=sk_test_your-stripe-key -STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secret +# X / Twitter API — Tweet → Video (ZORUNLU) +# https://developer.twitter.com → Project → Keys and Tokens +TWITTER_BEARER_TOKEN=your-twitter-bearer-token +TWITTER_API_KEY=your-twitter-api-key +TWITTER_API_SECRET=your-twitter-api-secret + +# ─── C# Media Worker ──────────────────────────────────────────────── + +# Worker'ın render tamamlandığında çağırdığı callback URL +# Local: http://localhost:3000/api/render-callback +# Production: https://api.contentgen.ai/api/render-callback +WORKER_CALLBACK_URL=http://localhost:3000/api/render-callback + +# Redis queue name (Worker ile aynı olmalı) +VIDEO_QUEUE_NAME=video-generation + +# ─── Stripe (Şimdilik devre dışı) ─────────────────────────────────── +# Stripe entegrasyonu ilerleyen aşamada eklenecek +# STRIPE_SECRET_KEY=sk_test_your-stripe-key +# STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secret diff --git a/src/modules/admin/admin.controller.ts b/src/modules/admin/admin.controller.ts index 788ba2a..5a3f7b3 100644 --- a/src/modules/admin/admin.controller.ts +++ b/src/modules/admin/admin.controller.ts @@ -316,9 +316,68 @@ export class AdminController { await this.prisma.rolePermission.deleteMany({ where: { roleId, permissionId }, }); - // Invalidate roles_list because permissions are nested in roles await this.cacheManager.del('roles_list'); return createSuccessResponse(null, 'Permission removed from role'); } + + // ================== Project Management (Admin) ================== + + @Get('projects') + @ApiOperation({ summary: 'Tüm projeleri getir (admin)' }) + async getAllProjects( + @Query() query: { page?: number; limit?: number; status?: string; userId?: string }, + ): Promise> { + const result = await this.adminService.getAllProjects({ + page: query.page ? Number(query.page) : 1, + limit: query.limit ? Number(query.limit) : 20, + status: query.status, + userId: query.userId, + }); + return createSuccessResponse(result); + } + + @Delete('projects/:id') + @ApiOperation({ summary: 'Projeyi sil (soft delete)' }) + async adminDeleteProject(@Param('id') id: string): Promise> { + const result = await this.adminService.adminDeleteProject(id); + return createSuccessResponse(result, 'Proje silindi'); + } + + // ================== Render Job Management (Admin) ================== + + @Get('render-jobs') + @ApiOperation({ summary: 'Tüm render jobları getir (admin)' }) + async getAllRenderJobs( + @Query() query: { page?: number; limit?: number; status?: string }, + ): Promise> { + const result = await this.adminService.getAllRenderJobs({ + page: query.page ? Number(query.page) : 1, + limit: query.limit ? Number(query.limit) : 20, + status: query.status, + }); + return createSuccessResponse(result); + } + + // ================== Ban / Activate User ================== + + @Put('users/:id/ban') + @ApiOperation({ summary: 'Kullanıcıyı banla' }) + async banUser(@Param('id') id: string): Promise> { + const user = await this.adminService.setUserActive(id, false); + return createSuccessResponse( + plainToInstance(UserResponseDto, user), + 'Kullanıcı banlandı', + ); + } + + @Put('users/:id/activate') + @ApiOperation({ summary: 'Kullanıcıyı aktif et' }) + async activateUser(@Param('id') id: string): Promise> { + const user = await this.adminService.setUserActive(id, true); + return createSuccessResponse( + plainToInstance(UserResponseDto, user), + 'Kullanıcı aktif edildi', + ); + } } diff --git a/src/modules/admin/admin.service.ts b/src/modules/admin/admin.service.ts index d357030..1ca96da 100644 --- a/src/modules/admin/admin.service.ts +++ b/src/modules/admin/admin.service.ts @@ -22,6 +22,8 @@ export class AdminService { storageStats, recentUsers, projectsByStatus, + totalRenderJobs, + renderJobsByStatus, ] = await Promise.all([ this.prisma.user.count(), this.prisma.user.count({ where: { isActive: true } }), @@ -37,6 +39,11 @@ export class AdminService { by: ['status'], _count: { id: true }, }), + this.prisma.renderJob.count(), + this.prisma.renderJob.groupBy({ + by: ['status'], + _count: { id: true }, + }), ]); // Kredi istatistikleri @@ -63,6 +70,13 @@ export class AdminService { return acc; }, {} as Record), }, + renderJobs: { + total: totalRenderJobs, + byStatus: renderJobsByStatus.reduce((acc, item) => { + acc[item.status] = item._count.id; + return acc; + }, {} as Record), + }, credits: { totalGranted: creditStats._sum.amount || 0, totalUsed: Math.abs(creditUsed._sum.amount || 0), @@ -106,6 +120,31 @@ export class AdminService { }); } + // ── Proje ve Render Yönetimi ────────────────────────────────────── + + async getAllProjects() { + return this.prisma.project.findMany({ + include: { user: { select: { email: true, firstName: true, lastName: true } } }, + orderBy: { createdAt: 'desc' }, + }); + } + + async getAllRenderJobs() { + return this.prisma.renderJob.findMany({ + include: { project: { select: { name: true } } }, + orderBy: { createdAt: 'desc' }, + }); + } + + // ── Kullanıcı Yönetimi ──────────────────────────────────────────── + + async banUser(userId: string, isBanned: boolean) { + return this.prisma.user.update({ + where: { id: userId }, + data: { isActive: !isBanned }, + }); + } + // ── Kullanıcı Kredi Yönetimi ────────────────────────────────────── async grantCredits(userId: string, amount: number, description: string) {