import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '../../database/prisma.service'; import { VideoGenerationProducer } from '../video-queue/video-generation.producer'; import { EventsGateway } from '../events/events.gateway'; @Injectable() export class DashboardService { private readonly logger = new Logger(DashboardService.name); constructor( private readonly db: PrismaService, private readonly videoGenerationProducer: VideoGenerationProducer, private readonly eventsGateway: EventsGateway, ) {} /** * Dashboard istatistiklerini hesaplar. * UserID bazlı filtreleme — her kullanıcı kendi verilerini görür. * Gerçek plan bilgisini Subscription → Plan tablosundan çeker. */ async getStats(userId: string) { const [ totalProjects, completedVideos, activeRenderJobs, failedProjects, draftProjects, totalCreditsUsed, recentProjects, activeSubscription, creditBalance, ] = await Promise.all([ // Toplam proje sayısı this.db.project.count({ where: { userId, deletedAt: null }, }), // Tamamlanan video sayısı this.db.project.count({ where: { userId, status: 'COMPLETED', deletedAt: null }, }), // Aktif render job sayısı this.db.project.count({ where: { userId, deletedAt: null, status: { in: ['PENDING', 'GENERATING_MEDIA', 'RENDERING', 'GENERATING_SCRIPT'] }, }, }), // Başarısız projeler this.db.project.count({ where: { userId, status: 'FAILED', deletedAt: null }, }), // Draft projeler this.db.project.count({ where: { userId, status: 'DRAFT', deletedAt: null }, }), // Toplam harcanan kredi this.db.project.aggregate({ where: { userId, deletedAt: null }, _sum: { creditsUsed: true }, }), // Son 5 proje this.db.project.findMany({ where: { userId, deletedAt: null }, orderBy: { createdAt: 'desc' }, take: 5, select: { id: true, title: true, status: true, progress: true, thumbnailUrl: true, finalVideoUrl: true, videoStyle: true, aspectRatio: true, language: true, sourceType: true, createdAt: true, updatedAt: true, completedAt: true, }, }), // Kullanıcının aktif aboneliği (plan dahil) this.db.subscription.findFirst({ where: { userId, status: { in: ['active', 'trialing'] }, }, include: { plan: true }, orderBy: { createdAt: 'desc' }, }), // Bu ayki net kredi bakiyesi (CreditTransaction tablosundan) this.db.creditTransaction.aggregate({ where: { userId, createdAt: { gte: new Date(new Date().getFullYear(), new Date().getMonth(), 1), }, }, _sum: { amount: true }, }), ]); // Plan bilgisini aktif abonelikten çek, yoksa free plan varsayılanları const plan = activeSubscription?.plan; const currentPlan = plan?.name || 'free'; const monthlyLimit = plan?.monthlyCredits ?? 3; const maxDuration = plan?.maxDuration ?? 30; const maxResolution = plan?.maxResolution ?? '720p'; // Kredi hesaplama: CreditTransaction tablosundan kalan bakiye const creditsUsed = totalCreditsUsed._sum.creditsUsed || 0; const netCreditBalance = creditBalance._sum.amount || 0; const creditsRemaining = Math.max(0, netCreditBalance); return { totalProjects, completedVideos, activeRenderJobs, failedProjects, draftProjects, totalCreditsUsed: creditsUsed, creditsRemaining, monthlyLimit, currentPlan, maxDuration, maxResolution, recentProjects, }; } /** * Kuyruk durumunu BullMQ ve Worker kuyruğundan alır. */ async getQueueStatus() { const queueStats = await this.videoGenerationProducer.getQueueStats(); const wsClients = this.eventsGateway.getConnectedClientsCount(); return { queue: queueStats, websocket: { connectedClients: wsClients, }, }; } /** * Bu ay üretilen videoların gün bazlı dağılımı (chart verisi). */ async getMonthlyChart(userId: string) { const startOfMonth = new Date(); startOfMonth.setDate(1); startOfMonth.setHours(0, 0, 0, 0); const projects = await this.db.project.findMany({ where: { userId, deletedAt: null, createdAt: { gte: startOfMonth }, }, select: { createdAt: true, status: true, }, orderBy: { createdAt: 'asc' }, }); // Gün bazlı gruplama const dailyMap: Record = {}; projects.forEach((p) => { const day = p.createdAt.toISOString().split('T')[0]; if (!dailyMap[day]) dailyMap[day] = { created: 0, completed: 0 }; dailyMap[day].created++; if (p.status === 'COMPLETED') dailyMap[day].completed++; }); return Object.entries(dailyMap).map(([date, counts]) => ({ date, ...counts, })); } }