Spaces:
Configuration error
Configuration error
| import { NextRequest, NextResponse } from "next/server" | |
| import prisma from "@/lib/prisma" | |
| import { stackServerApp } from "@/lib/stack-server" | |
| // GET - Get detailed analytics for a creator | |
| export async function GET(request: NextRequest) { | |
| try { | |
| const user = await stackServerApp.getUser() | |
| if (!user) { | |
| return NextResponse.json( | |
| { error: "Unauthorized" }, | |
| { status: 401 } | |
| ) | |
| } | |
| const { searchParams } = new URL(request.url) | |
| const days = parseInt(searchParams.get("days") || "30") | |
| const daysAgo = new Date() | |
| daysAgo.setDate(daysAgo.getDate() - days) | |
| // Get all prompts by this user | |
| const prompts = await prisma.prompt.findMany({ | |
| where: { creatorId: user.id }, | |
| select: { | |
| id: true, | |
| title: true, | |
| slug: true, | |
| totalRuns: true, | |
| starsCount: true, | |
| remixesCount: true, | |
| framework: true, | |
| badges: true, | |
| parentId: true, | |
| createdAt: true, | |
| }, | |
| orderBy: { totalRuns: "desc" }, | |
| }) | |
| // Get runs data for the period | |
| const runs = await prisma.run.findMany({ | |
| where: { | |
| prompt: { creatorId: user.id }, | |
| createdAt: { gte: daysAgo }, | |
| }, | |
| select: { | |
| id: true, | |
| model: true, | |
| tokens: true, | |
| cached: true, | |
| createdAt: true, | |
| promptId: true, | |
| }, | |
| orderBy: { createdAt: "desc" }, | |
| }) | |
| // Get engagement metrics (errors tracked separately) | |
| const engagementMetrics = await prisma.engagementMetric.findMany({ | |
| where: { | |
| targetType: "prompt", | |
| targetId: { in: prompts.map(p => p.id) }, | |
| createdAt: { gte: daysAgo }, | |
| }, | |
| select: { | |
| action: true, | |
| createdAt: true, | |
| targetId: true, | |
| } | |
| }) | |
| // Aggregate runs by day | |
| const runsByDay: Record<string, { runs: number; errors: number; tokens: number }> = {} | |
| runs.forEach(run => { | |
| const day = run.createdAt.toISOString().split("T")[0] | |
| if (!runsByDay[day]) { | |
| runsByDay[day] = { runs: 0, errors: 0, tokens: 0 } | |
| } | |
| runsByDay[day].runs += 1 | |
| runsByDay[day].tokens += run.tokens || 0 | |
| }) | |
| // Prepare daily data for chart | |
| const dailyData = [] | |
| for (let i = days - 1; i >= 0; i--) { | |
| const date = new Date() | |
| date.setDate(date.getDate() - i) | |
| const dateStr = date.toISOString().split("T")[0] | |
| const dayData = runsByDay[dateStr] || { runs: 0, errors: 0, tokens: 0 } | |
| dailyData.push({ | |
| date: dateStr, | |
| runs: dayData.runs, | |
| errors: dayData.errors, | |
| tokens: dayData.tokens, | |
| }) | |
| } | |
| // Model usage stats | |
| const modelUsage: Record<string, { runs: number; tokens: number }> = {} | |
| runs.forEach(run => { | |
| if (!modelUsage[run.model]) { | |
| modelUsage[run.model] = { runs: 0, tokens: 0 } | |
| } | |
| modelUsage[run.model].runs += 1 | |
| modelUsage[run.model].tokens += run.tokens || 0 | |
| }) | |
| // Token usage per prompt | |
| const tokensByPrompt: Record<string, number> = {} | |
| runs.forEach(run => { | |
| if (!tokensByPrompt[run.promptId]) { | |
| tokensByPrompt[run.promptId] = 0 | |
| } | |
| tokensByPrompt[run.promptId] += run.tokens || 0 | |
| }) | |
| // Remix traffic - find remixes of user's prompts | |
| const remixTraffic = await prisma.prompt.findMany({ | |
| where: { | |
| parentId: { in: prompts.map(p => p.id) }, | |
| createdAt: { gte: daysAgo }, | |
| }, | |
| select: { | |
| id: true, | |
| title: true, | |
| parentId: true, | |
| totalRuns: true, | |
| createdAt: true, | |
| }, | |
| orderBy: { createdAt: "desc" }, | |
| take: 20, | |
| }) | |
| // Engagement breakdown | |
| const engagementByAction: Record<string, number> = {} | |
| engagementMetrics.forEach(metric => { | |
| engagementByAction[metric.action] = (engagementByAction[metric.action] || 0) + 1 | |
| }) | |
| // Total tokens used | |
| const totalTokens = runs.reduce((sum, run) => sum + (run.tokens || 0), 0) | |
| const cachedRuns = runs.filter(r => r.cached).length | |
| const cacheHitRate = runs.length > 0 ? (cachedRuns / runs.length) * 100 : 0 | |
| // Calculate totals | |
| const totalRuns = prompts.reduce((sum, p) => sum + p.totalRuns, 0) | |
| const totalStars = prompts.reduce((sum, p) => sum + p.starsCount, 0) | |
| const totalRemixes = prompts.reduce((sum, p) => sum + p.remixesCount, 0) | |
| // Error rate estimation (based on low token runs which might indicate errors) | |
| const potentialErrors = runs.filter(r => r.tokens !== null && r.tokens < 10).length | |
| const errorRate = runs.length > 0 ? (potentialErrors / runs.length) * 100 : 0 | |
| return NextResponse.json({ | |
| prompts: prompts.map(p => ({ | |
| ...p, | |
| tokens: tokensByPrompt[p.id] || 0, | |
| })), | |
| dailyData, | |
| modelUsage, | |
| totalTokens, | |
| cacheHitRate, | |
| errorRate, | |
| remixTraffic, | |
| engagementByAction, | |
| totals: { | |
| runs: totalRuns, | |
| stars: totalStars, | |
| remixes: totalRemixes, | |
| prompts: prompts.length, | |
| periodRuns: runs.length, | |
| periodTokens: totalTokens, | |
| }, | |
| }) | |
| } catch (error) { | |
| console.error("Error fetching analytics:", error) | |
| return NextResponse.json( | |
| { error: "Failed to fetch analytics" }, | |
| { status: 500 } | |
| ) | |
| } | |
| } | |