Skip to content

Commit e46b68c

Browse files
feat: candle tributes + stats counter, API consolidation
- Add /api/candle route + useCandles hook for per-repo candle/flower tributes (optimistic local state, Redis-backed totals). - Add StatsCounter and GitHubIcon components. - Remove dynamic /api/badge/[owner]/[repo] and /api/recent-profiles routes; fold their behavior into the remaining endpoints. - UI pass across page.tsx, UserDashboard, CertificateCard, RecentlyBuried, ScannerBanner, SearchForm, PageHero, ReadmeBadge, SiteFooter, ClickSpark, and globals.css. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 8bede24 commit e46b68c

20 files changed

Lines changed: 851 additions & 1068 deletions

src/app/api/badge/[owner]/[repo]/route.ts

Lines changed: 0 additions & 130 deletions
This file was deleted.

src/app/api/badge/route.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ function buildSvg(username: string, dead: number, struggling: number, alive: num
4141
const aliveW = total === 0 ? BAR_W : BAR_W - deadW - strugglingW
4242
const MONO = "'Courier New','Courier',ui-monospace,monospace"
4343

44-
return `<svg width="440" height="128" viewBox="0 0 440 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Graveyard report for @${username}">
44+
return `<svg width="440" height="96" viewBox="0 0 440 96" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Graveyard report for @${username}">
4545
<defs>
4646
<clipPath id="bar-clip"><rect x="${BAR_X}" y="${BAR_Y}" width="${BAR_W}" height="${BAR_H}"/></clipPath>
4747
</defs>
48-
<rect width="440" height="128" fill="#FAF6EF"/>
49-
<rect x="1.5" y="1.5" width="437" height="125" fill="none" stroke="#0a0a0a" stroke-width="3"/>
48+
<rect width="440" height="96" fill="#FAF6EF"/>
49+
<rect x="1" y="1" width="438" height="94" fill="none" stroke="#1a1a1a" stroke-width="2"/>
5050
5151
<text x="16" y="28" font-family=${JSON.stringify(MONO)} font-size="9" font-weight="700" fill="#9a9288" letter-spacing="2.2">🪦 GITHUB REPO GRAVEYARD</text>
5252
@@ -60,8 +60,6 @@ function buildSvg(username: string, dead: number, struggling: number, alive: num
6060
${strugglingW > 0 ? `<rect x="${BAR_X + deadW}" y="${BAR_Y}" width="${strugglingW}" height="${BAR_H}" fill="#b45309"/>` : ''}
6161
${aliveW > 0 ? `<rect x="${BAR_X + deadW + strugglingW}" y="${BAR_Y}" width="${aliveW}" height="${BAR_H}" fill="#2d7a3c"/>` : ''}
6262
</g>
63-
64-
<text x="16" y="104" font-family=${JSON.stringify(MONO)} font-size="10" font-weight="500" fill="#7a7268" letter-spacing="1.2">commitmentissues.dev</text>
6563
</svg>`
6664
}
6765

@@ -79,7 +77,8 @@ export async function GET(req: NextRequest) {
7977
return new NextResponse(svg, {
8078
headers: {
8179
'Content-Type': 'image/svg+xml',
82-
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
80+
// Short edge cache so camo/README updates pick up within minutes, not hours
81+
'Cache-Control': 'public, max-age=300, s-maxage=300, stale-while-revalidate=900',
8382
},
8483
})
8584
}

src/app/api/candle/route.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
3+
const VALID_FULLNAME = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/
4+
5+
async function getRedis() {
6+
if (!process.env.KV_REST_API_URL || !process.env.KV_REST_API_TOKEN) return null
7+
try {
8+
const { Redis } = await import('@upstash/redis')
9+
return new Redis({ url: process.env.KV_REST_API_URL, token: process.env.KV_REST_API_TOKEN })
10+
} catch { return null }
11+
}
12+
13+
export async function GET() {
14+
const redis = await getRedis()
15+
if (!redis) return NextResponse.json({ totals: {} })
16+
try {
17+
const totals = await redis.hgetall<Record<string, string>>('candles:by_repo')
18+
const numeric: Record<string, number> = {}
19+
if (totals) {
20+
for (const [k, v] of Object.entries(totals)) {
21+
const n = typeof v === 'number' ? v : Number(v)
22+
if (Number.isFinite(n)) numeric[k] = n
23+
}
24+
}
25+
return NextResponse.json({ totals: numeric })
26+
} catch {
27+
return NextResponse.json({ totals: {} })
28+
}
29+
}
30+
31+
export async function POST(request: NextRequest) {
32+
let body: { fullName?: string; action?: 'add' | 'remove' }
33+
try { body = await request.json() } catch {
34+
return NextResponse.json({ error: 'Invalid request.' }, { status: 400 })
35+
}
36+
const fullName = body?.fullName?.trim() ?? ''
37+
if (!fullName || !VALID_FULLNAME.test(fullName)) {
38+
return NextResponse.json({ error: 'Invalid repo name.' }, { status: 400 })
39+
}
40+
const action = body?.action === 'remove' ? 'remove' : 'add'
41+
42+
const redis = await getRedis()
43+
if (!redis) return NextResponse.json({ count: action === 'add' ? 1 : 0, stored: false })
44+
45+
try {
46+
if (action === 'remove') {
47+
const next = await redis.hincrby('candles:by_repo', fullName, -1)
48+
if (next < 0) {
49+
// Floor at 0 — someone tried to remove past zero
50+
await redis.hset('candles:by_repo', { [fullName]: 0 })
51+
return NextResponse.json({ count: 0, stored: true })
52+
}
53+
return NextResponse.json({ count: next, stored: true })
54+
}
55+
const count = await redis.hincrby('candles:by_repo', fullName, 1)
56+
return NextResponse.json({ count, stored: true })
57+
} catch {
58+
return NextResponse.json({ count: action === 'add' ? 1 : 0, stored: false })
59+
}
60+
}

src/app/api/recent-profiles/route.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/app/api/repo/route.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,7 @@ export async function GET(request: NextRequest) {
177177
analyzedAt: new Date().toISOString(),
178178
deathDate: formatDate(repoData.pushedAt),
179179
lastWords,
180-
}).catch((err) => {
181-
console.error('[addRecent] Redis write failed:', err)
182-
})
180+
}).catch(() => { /* non-critical: Redis write failures shouldn't fail the request */ })
183181

184182
return NextResponse.json(certificate)
185183
}

0 commit comments

Comments
 (0)