generated from fahricansecer/boilerplate-be
This commit is contained in:
190
src/modules/dashboard/dashboard.service.ts
Normal file
190
src/modules/dashboard/dashboard.service.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
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<string, { created: number; completed: number }> = {};
|
||||
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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user