Spaces:
Running
Running
Download apps/api/dist/analytics/analytics.service.js from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 13.6 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/dist/analytics/analytics.service.js
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/api/dist/analytics/analytics.service.js
-
curl -L -o analytics.service.js https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/dist/analytics/analytics.service.js
13.6 kB
| ; | |
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | |
| var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | |
| if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | |
| else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | |
| return c > 3 && r && Object.defineProperty(target, key, r), r; | |
| }; | |
| var __metadata = (this && this.__metadata) || function (k, v) { | |
| if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); | |
| }; | |
| var AnalyticsService_1; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.AnalyticsService = void 0; | |
| const common_1 = require("@nestjs/common"); | |
| const prisma_service_1 = require("../prisma/prisma.service"); | |
| const authorization_service_1 = require("../common/authorization.service"); | |
| let AnalyticsService = AnalyticsService_1 = class AnalyticsService { | |
| prisma; | |
| authorizationService; | |
| logger = new common_1.Logger(AnalyticsService_1.name); | |
| cache = new Map(); | |
| cacheTTL = 30_000; | |
| constructor(prisma, authorizationService) { | |
| this.prisma = prisma; | |
| this.authorizationService = authorizationService; | |
| } | |
| getCached(key) { | |
| const entry = this.cache.get(key); | |
| if (entry && entry.expiresAt > Date.now()) | |
| return entry.data; | |
| this.cache.delete(key); | |
| return null; | |
| } | |
| setCache(key, data) { | |
| this.cache.set(key, { data, expiresAt: Date.now() + this.cacheTTL }); | |
| if (this.cache.size > 100) { | |
| const oldest = this.cache.keys().next().value; | |
| if (oldest) | |
| this.cache.delete(oldest); | |
| } | |
| } | |
| async getDashboardMetrics(podcastId, userId) { | |
| await this.authorizationService.checkPodcastAccess(podcastId, userId); | |
| const cached = this.getCached(`dashboard:${podcastId}`); | |
| if (cached) | |
| return cached; | |
| const [totalGuests, guestsByStatus, totalEpisodes, episodesByStatus, upcomingBookings, recentGuests, topPriorityGuests,] = await Promise.all([ | |
| this.prisma.guest.count({ where: { podcastId } }), | |
| this.prisma.guest.groupBy({ | |
| by: ["status"], | |
| where: { podcastId }, | |
| _count: { id: true }, | |
| }), | |
| this.prisma.episode.count({ where: { podcastId } }), | |
| this.prisma.episode.groupBy({ | |
| by: ["status"], | |
| where: { podcastId }, | |
| _count: { id: true }, | |
| }), | |
| this.prisma.booking.count({ | |
| where: { | |
| episode: { podcastId }, | |
| startTime: { gte: new Date() }, | |
| }, | |
| }), | |
| this.prisma.guest.findMany({ | |
| where: { podcastId }, | |
| orderBy: { createdAt: "desc" }, | |
| take: 5, | |
| select: { | |
| id: true, | |
| fullName: true, | |
| email: true, | |
| company: true, | |
| status: true, | |
| priorityScore: true, | |
| createdAt: true, | |
| }, | |
| }), | |
| this.prisma.guest.findMany({ | |
| where: { podcastId, status: { in: ["prospect", "contacted"] } }, | |
| orderBy: { priorityScore: "desc" }, | |
| take: 5, | |
| select: { | |
| id: true, | |
| fullName: true, | |
| company: true, | |
| title: true, | |
| priorityScore: true, | |
| }, | |
| }), | |
| ]); | |
| const guestStatusMap = {}; | |
| for (const item of guestsByStatus) { | |
| guestStatusMap[item.status] = item._count.id; | |
| } | |
| const episodeStatusMap = {}; | |
| for (const item of episodesByStatus) { | |
| episodeStatusMap[item.status] = item._count.id; | |
| } | |
| const result = { | |
| totalGuests, | |
| guestsByStatus: guestStatusMap, | |
| totalEpisodes, | |
| episodesByStatus: episodeStatusMap, | |
| upcomingBookings, | |
| recentGuests, | |
| topPriorityGuests, | |
| }; | |
| this.setCache(`dashboard:${podcastId}`, result); | |
| return result; | |
| } | |
| async getGuestPipeline(podcastId, userId) { | |
| await this.authorizationService.checkPodcastAccess(podcastId, userId); | |
| const cached = this.getCached(`pipeline:${podcastId}`); | |
| if (cached) | |
| return cached; | |
| const pipeline = await this.prisma.guest.groupBy({ | |
| by: ["status"], | |
| where: { podcastId }, | |
| _count: { id: true }, | |
| _avg: { priorityScore: true }, | |
| }); | |
| const result = pipeline.map((stage) => ({ | |
| status: stage.status, | |
| count: stage._count.id, | |
| avgScore: Math.round(stage._avg.priorityScore || 0), | |
| })); | |
| this.setCache(`pipeline:${podcastId}`, result); | |
| return result; | |
| } | |
| async getOutreachMetrics(podcastId, userId) { | |
| await this.authorizationService.checkPodcastAccess(podcastId, userId); | |
| const cached = this.getCached(`outreach:${podcastId}`); | |
| if (cached) | |
| return cached; | |
| const guests = await this.prisma.guest.findMany({ | |
| where: { podcastId }, | |
| select: { id: true }, | |
| }); | |
| const guestIds = guests.map((g) => g.id); | |
| if (guestIds.length === 0) { | |
| return { | |
| totalSequences: 0, | |
| activeSequences: 0, | |
| totalMessagesSent: 0, | |
| openRate: 0, | |
| clickRate: 0, | |
| replyRate: 0, | |
| }; | |
| } | |
| const [totalSequences, activeSequences, messageStats] = await Promise.all([ | |
| this.prisma.outreachSequence.count({ | |
| where: { guestId: { in: guestIds } }, | |
| }), | |
| this.prisma.outreachSequence.count({ | |
| where: { guestId: { in: guestIds }, status: "active" }, | |
| }), | |
| this.prisma.outreachMessage.aggregate({ | |
| where: { sequence: { guestId: { in: guestIds } } }, | |
| _count: { id: true, sentAt: true, openedAt: true, clickedAt: true }, | |
| }), | |
| ]); | |
| const sent = messageStats._count.sentAt; | |
| const opened = messageStats._count.openedAt; | |
| const clicked = messageStats._count.clickedAt; | |
| const result = { | |
| totalSequences, | |
| activeSequences, | |
| totalMessagesSent: sent, | |
| openRate: sent > 0 ? Math.round((opened / sent) * 100) : 0, | |
| clickRate: sent > 0 ? Math.round((clicked / sent) * 100) : 0, | |
| }; | |
| this.setCache(`outreach:${podcastId}`, result); | |
| return result; | |
| } | |
| async getSchedulingMetrics(podcastId, userId, daysBack = 90) { | |
| await this.authorizationService.checkPodcastAccess(podcastId, userId); | |
| const cacheKey = `scheduling:${podcastId}:${daysBack}`; | |
| const cached = this.getCached(cacheKey); | |
| if (cached) | |
| return cached; | |
| const since = new Date(Date.now() - daysBack * 24 * 60 * 60 * 1000); | |
| const [scheduledCount, recordedCount, publishedCount, avgDuration] = await Promise.all([ | |
| this.prisma.episode.count({ | |
| where: { | |
| podcastId, | |
| status: "scheduled", | |
| createdAt: { gte: since }, | |
| }, | |
| }), | |
| this.prisma.episode.count({ | |
| where: { | |
| podcastId, | |
| status: "recorded", | |
| createdAt: { gte: since }, | |
| }, | |
| }), | |
| this.prisma.episode.count({ | |
| where: { | |
| podcastId, | |
| status: "published", | |
| createdAt: { gte: since }, | |
| }, | |
| }), | |
| this.prisma.episode.aggregate({ | |
| where: { podcastId, durationMinutes: { not: null } }, | |
| _avg: { durationMinutes: true }, | |
| }), | |
| ]); | |
| const result = { | |
| scheduledCount, | |
| recordedCount, | |
| publishedCount, | |
| conversionRate: scheduledCount > 0 | |
| ? Math.round((recordedCount / scheduledCount) * 100) | |
| : 0, | |
| avgDurationMinutes: Math.round(avgDuration._avg.durationMinutes || 0), | |
| }; | |
| this.setCache(cacheKey, result); | |
| return result; | |
| } | |
| async getRecentActivity(podcastId, userId, limit = 20) { | |
| await this.authorizationService.checkPodcastAccess(podcastId, userId); | |
| const cacheKey = `recent:${podcastId}:${limit}`; | |
| const cached = this.getCached(cacheKey); | |
| if (cached) | |
| return cached; | |
| const [recentGuests, recentEpisodes, recentBookings, recentOutreach] = await Promise.all([ | |
| this.prisma.guest.findMany({ | |
| where: { podcastId }, | |
| orderBy: { createdAt: "desc" }, | |
| take: limit, | |
| select: { | |
| id: true, | |
| fullName: true, | |
| status: true, | |
| createdAt: true, | |
| _count: { | |
| select: { | |
| outreachSequences: true, | |
| }, | |
| }, | |
| }, | |
| }), | |
| this.prisma.episode.findMany({ | |
| where: { podcastId }, | |
| orderBy: { createdAt: "desc" }, | |
| take: limit, | |
| select: { | |
| id: true, | |
| title: true, | |
| status: true, | |
| scheduledAt: true, | |
| createdAt: true, | |
| guest: { | |
| select: { | |
| fullName: true, | |
| }, | |
| }, | |
| }, | |
| }), | |
| this.prisma.booking.findMany({ | |
| where: { episode: { podcastId } }, | |
| orderBy: { createdAt: "desc" }, | |
| take: limit, | |
| select: { | |
| id: true, | |
| startTime: true, | |
| isConfirmed: true, | |
| createdAt: true, | |
| episode: { | |
| select: { | |
| title: true, | |
| guest: { | |
| select: { | |
| fullName: true, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }), | |
| this.prisma.outreachMessage.findMany({ | |
| where: { sequence: { guest: { podcastId } } }, | |
| orderBy: { sentAt: "desc" }, | |
| take: limit, | |
| select: { | |
| id: true, | |
| type: true, | |
| sentAt: true, | |
| openedAt: true, | |
| sequence: { | |
| select: { | |
| guest: { | |
| select: { | |
| fullName: true, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }), | |
| ]); | |
| const activities = [ | |
| ...recentGuests.map((g) => ({ | |
| type: "guest_added", | |
| id: g.id, | |
| title: `New guest: ${g.fullName}`, | |
| status: g.status, | |
| timestamp: g.createdAt, | |
| })), | |
| ...recentEpisodes.map((e) => ({ | |
| type: "episode_created", | |
| id: e.id, | |
| title: `Episode: ${e.title}`, | |
| subtitle: `With ${e.guest.fullName}`, | |
| status: e.status, | |
| timestamp: e.createdAt, | |
| scheduledAt: e.scheduledAt, | |
| })), | |
| ...recentBookings.map((b) => ({ | |
| type: "booking_created", | |
| id: b.id, | |
| title: `Booking: ${b.episode.title}`, | |
| subtitle: `With ${b.episode.guest.fullName}`, | |
| timestamp: b.createdAt, | |
| startTime: b.startTime, | |
| isConfirmed: b.isConfirmed, | |
| })), | |
| ...recentOutreach.map((o) => ({ | |
| type: "message_sent", | |
| id: o.id, | |
| title: `Email sent to ${o.sequence.guest.fullName}`, | |
| subtitle: o.type, | |
| timestamp: o.sentAt, | |
| openedAt: o.openedAt, | |
| })), | |
| ]; | |
| const result = activities | |
| .filter((a) => a.timestamp != null) | |
| .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) | |
| .slice(0, limit); | |
| this.setCache(cacheKey, result); | |
| return result; | |
| } | |
| }; | |
| exports.AnalyticsService = AnalyticsService; | |
| exports.AnalyticsService = AnalyticsService = AnalyticsService_1 = __decorate([ | |
| (0, common_1.Injectable)(), | |
| __metadata("design:paramtypes", [prisma_service_1.PrismaService, | |
| authorization_service_1.AuthorizationService]) | |
| ], AnalyticsService); | |
| //# sourceMappingURL=analytics.service.js.map |