Skip to content

Commit 15d8325

Browse files
authored
Merge pull request #89 from devakone/devakone/review-community-stats-prd
feat(community): add community stats pipeline and public page
2 parents fa87d22 + c7fce4d commit 15d8325

13 files changed

Lines changed: 1071 additions & 2 deletions

File tree

apps/web/src/app/AppHeader.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ export default function AppHeader(props: {
3535
? [
3636
{ href: "/", label: "My VCP" },
3737
{ href: "/vibes", label: "Repo VCPs" },
38+
{ href: "/community", label: "Community" },
3839
]
3940
: [
4041
{ href: "/", label: "Home" },
4142
{ href: "/methodology", label: "Methodology" },
4243
{ href: "/security", label: "Security" },
44+
{ href: "/community", label: "Community" },
4345
];
4446

4547
const links = props.isAdmin
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { NextResponse } from "next/server";
2+
import { createClient } from "@supabase/supabase-js";
3+
4+
export const runtime = "nodejs";
5+
6+
/**
7+
* GET /api/community/stats
8+
*
9+
* Returns anonymized, aggregated community statistics.
10+
* No authentication required — publicly accessible.
11+
* Payload is precomputed by the hourly Inngest rollup function.
12+
*
13+
* Uses a raw Supabase client (not typed) because community_rollups
14+
* is not yet in the generated database types.
15+
*/
16+
export async function GET() {
17+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
18+
const key = process.env.SUPABASE_SERVICE_ROLE_KEY;
19+
20+
if (!url || !key) {
21+
return NextResponse.json({ error: "server_misconfigured" }, { status: 500 });
22+
}
23+
24+
const supabase = createClient(url, key);
25+
26+
const { data, error } = await supabase
27+
.from("community_rollups")
28+
.select("payload_json")
29+
.eq("rollup_window", "30d")
30+
.order("as_of_date", { ascending: false })
31+
.limit(1)
32+
.maybeSingle();
33+
34+
if (error) {
35+
console.error("Failed to fetch community rollup:", error.message);
36+
return NextResponse.json(
37+
{ error: "internal_error" },
38+
{ status: 500 }
39+
);
40+
}
41+
42+
if (!data) {
43+
return NextResponse.json(
44+
{
45+
suppressed: true,
46+
reason: "no_data_yet",
47+
eligible_profiles: 0,
48+
threshold: 10,
49+
},
50+
{
51+
headers: {
52+
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
53+
},
54+
}
55+
);
56+
}
57+
58+
return NextResponse.json(data.payload_json, {
59+
headers: {
60+
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=3600",
61+
},
62+
});
63+
}

apps/web/src/app/api/inngest/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { serve } from "inngest/next";
22
import { inngest } from "@/inngest/client";
3-
import { analyzeRepo } from "@/inngest/functions";
3+
import { analyzeRepo, computeCommunityRollupFn } from "@/inngest/functions";
44

55
/**
66
* Inngest API route
@@ -15,5 +15,5 @@ import { analyzeRepo } from "@/inngest/functions";
1515
*/
1616
export const { GET, POST, PUT } = serve({
1717
client: inngest,
18-
functions: [analyzeRepo],
18+
functions: [analyzeRepo, computeCommunityRollupFn],
1919
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Link from "next/link";
2+
3+
/**
4+
* Lightweight layout for the public community page.
5+
* No auth required, minimal header with app logo/link.
6+
*/
7+
export default function CommunityLayout({
8+
children,
9+
}: {
10+
children: React.ReactNode;
11+
}) {
12+
return (
13+
<div className="min-h-screen bg-[#faf9fc]">
14+
{/* Minimal header */}
15+
<header className="border-b border-black/5 bg-white/80 backdrop-blur-sm">
16+
<div className="mx-auto flex max-w-5xl items-center justify-between px-6 py-3">
17+
<Link
18+
href="/"
19+
className="text-sm font-semibold tracking-tight text-zinc-900 transition hover:text-violet-600"
20+
>
21+
Vibe Coding Profiler
22+
</Link>
23+
<Link
24+
href="/login"
25+
className="rounded-full bg-gradient-to-r from-violet-600 to-indigo-500 px-4 py-1.5 text-xs font-semibold text-white shadow-sm transition hover:brightness-110"
26+
>
27+
Get your VCP
28+
</Link>
29+
</div>
30+
</header>
31+
<main>{children}</main>
32+
</div>
33+
);
34+
}

0 commit comments

Comments
 (0)