|
| 1 | +import type { Metadata } from "next"; |
1 | 2 | import AnalysisClient from "./AnalysisClient"; |
2 | 3 | import { createSupabaseServerClient } from "@/lib/supabase/server"; |
3 | 4 | import { redirect } from "next/navigation"; |
4 | 5 | import { wrappedTheme } from "@/lib/theme"; |
5 | 6 |
|
6 | 7 | export const runtime = "nodejs"; |
7 | 8 |
|
| 9 | +const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:8108"; |
| 10 | + |
| 11 | +// Database row types for OG metadata |
| 12 | +type AnalysisJobRow = { |
| 13 | + repo_id: string; |
| 14 | + commit_count: number | null; |
| 15 | + status: string; |
| 16 | +}; |
| 17 | + |
| 18 | +type RepoRow = { |
| 19 | + full_name: string; |
| 20 | +}; |
| 21 | + |
| 22 | +type VibeInsightsRow = { |
| 23 | + persona_name: string | null; |
| 24 | + persona_tagline: string | null; |
| 25 | +}; |
| 26 | + |
| 27 | +type AnalysisInsightsRow = { |
| 28 | + persona_label: string | null; |
| 29 | + share_template: unknown; |
| 30 | +}; |
| 31 | + |
| 32 | +export async function generateMetadata({ |
| 33 | + params, |
| 34 | +}: { |
| 35 | + params: Promise<{ jobId: string }>; |
| 36 | +}): Promise<Metadata> { |
| 37 | + const { jobId } = await params; |
| 38 | + const supabase = await createSupabaseServerClient(); |
| 39 | + |
| 40 | + // Fetch job and related data for OG metadata |
| 41 | + const { data: jobData } = await supabase |
| 42 | + .from("analysis_jobs") |
| 43 | + .select("repo_id, commit_count, status") |
| 44 | + .eq("id", jobId) |
| 45 | + .maybeSingle(); |
| 46 | + |
| 47 | + const job = jobData as AnalysisJobRow | null; |
| 48 | + |
| 49 | + if (!job || job.status !== "done") { |
| 50 | + return { |
| 51 | + title: "Analysis | Vibe Coding Profiler", |
| 52 | + description: "Analyzing your commit history to discover your Vibe Coding Profile.", |
| 53 | + }; |
| 54 | + } |
| 55 | + |
| 56 | + // Fetch persona and repo info |
| 57 | + const [repoResult, vibeResult, insightsResult] = await Promise.all([ |
| 58 | + supabase.from("repos").select("full_name").eq("id", job.repo_id).maybeSingle(), |
| 59 | + supabase |
| 60 | + .from("vibe_insights") |
| 61 | + .select("persona_name, persona_tagline") |
| 62 | + .eq("job_id", jobId) |
| 63 | + .maybeSingle(), |
| 64 | + supabase |
| 65 | + .from("analysis_insights") |
| 66 | + .select("persona_label, share_template") |
| 67 | + .eq("job_id", jobId) |
| 68 | + .maybeSingle(), |
| 69 | + ]); |
| 70 | + |
| 71 | + const repoRow = repoResult?.data as RepoRow | null; |
| 72 | + const vibeRow = vibeResult?.data as VibeInsightsRow | null; |
| 73 | + const insightsRow = insightsResult?.data as AnalysisInsightsRow | null; |
| 74 | + |
| 75 | + const repoName = |
| 76 | + repoRow?.full_name && typeof repoRow.full_name === "string" |
| 77 | + ? repoRow.full_name |
| 78 | + : "Repository"; |
| 79 | + |
| 80 | + const personaName = |
| 81 | + vibeRow?.persona_name ?? |
| 82 | + insightsRow?.persona_label ?? |
| 83 | + "Vibe Coder"; |
| 84 | + |
| 85 | + const personaTagline = |
| 86 | + vibeRow?.persona_tagline ?? |
| 87 | + (insightsRow?.share_template as { tagline?: string } | null)?.tagline ?? |
| 88 | + "Discover your AI coding style"; |
| 89 | + |
| 90 | + const title = `${personaName} | ${repoName} VCP`; |
| 91 | + const description = `${personaTagline} — ${job.commit_count?.toLocaleString() ?? 0} commits analyzed.`; |
| 92 | + const ogImageUrl = `${appUrl}/api/og/analysis/${jobId}`; |
| 93 | + const pageUrl = `${appUrl}/analysis/${jobId}`; |
| 94 | + |
| 95 | + return { |
| 96 | + title, |
| 97 | + description, |
| 98 | + openGraph: { |
| 99 | + title, |
| 100 | + description, |
| 101 | + url: pageUrl, |
| 102 | + siteName: "Vibe Coding Profiler", |
| 103 | + type: "article", |
| 104 | + images: [ |
| 105 | + { |
| 106 | + url: ogImageUrl, |
| 107 | + width: 1200, |
| 108 | + height: 630, |
| 109 | + alt: `${personaName} - Vibe Coding Profile for ${repoName}`, |
| 110 | + }, |
| 111 | + ], |
| 112 | + }, |
| 113 | + twitter: { |
| 114 | + card: "summary_large_image", |
| 115 | + title, |
| 116 | + description, |
| 117 | + images: [ogImageUrl], |
| 118 | + }, |
| 119 | + }; |
| 120 | +} |
| 121 | + |
8 | 122 | export default async function AnalysisPage({ |
9 | 123 | params, |
10 | 124 | }: { |
|
0 commit comments