Files
skillhub/apps/web/app/api/review/stats/route.ts
airano-ir 16ee9e3beb sync: malware page, security alerts, review diagnostics, skill detail improvements
- Add malware security advisory page and SecurityAlertBanner component
- Add review diagnostics API and reviewed stats page
- Improve skill detail page with better scoring display and metadata
- Update review API endpoints with enhanced filtering and stats
- Add skill file serving improvements and cache enhancements
- Remove legacy curation scripts (moved to internal tooling)
- Update CLI search with score filtering support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:08:12 +02:00

79 lines
2.3 KiB
TypeScript

import { type NextRequest, NextResponse } from 'next/server';
import { createDb, skillReviewQueries } from '@skillhub/db';
import { requireAdmin } from '@/lib/admin-auth';
import { withRateLimit, createRateLimitResponse, createRateLimitHeaders } from '@/lib/rate-limit';
import { getCached, setCache, cacheKeys, cacheTTL } from '@/lib/cache';
const db = createDb();
interface ReviewStatsData {
total_skills: number;
ai_reviewed: number;
needs_re_review: number;
total_reviews: number;
}
/**
* GET /api/review/stats
* Returns review pipeline status statistics.
*/
export async function GET(request: NextRequest) {
// Rate limiting
const rateLimitResult = await withRateLimit(request, 'anonymous');
if (!rateLimitResult.allowed) {
return createRateLimitResponse(rateLimitResult);
}
// Admin check (supports API key auth via Authorization header)
const adminCheck = await requireAdmin(request);
if (!adminCheck.authorized) {
return adminCheck.response;
}
try {
// Check cache
const cacheKey = cacheKeys.reviewStats();
const cached = await getCached<ReviewStatsData>(cacheKey);
if (cached) {
return NextResponse.json(cached, {
headers: {
'X-Cache': 'HIT',
...createRateLimitHeaders(rateLimitResult),
},
});
}
// Run pipeline stats and total reviews count in parallel
const [statusCounts, totalReviews] = await Promise.all([
skillReviewQueries.getPublicPipelineStats(db),
skillReviewQueries.countTotalReviews(db),
]);
// total_skills = sum of all statuses from pipeline query (browse-ready SKILL.md)
const totalSkills = Object.values(statusCounts).reduce((sum, n) => sum + n, 0);
const data: ReviewStatsData = {
total_skills: totalSkills,
ai_reviewed: statusCounts['ai-reviewed'] ?? 0,
needs_re_review: statusCounts['needs-re-review'] ?? 0,
total_reviews: totalReviews,
};
// Cache for 60 seconds
await setCache(cacheKey, data, cacheTTL.reviewStats);
return NextResponse.json(data, {
headers: {
'X-Cache': 'MISS',
...createRateLimitHeaders(rateLimitResult),
},
});
} catch (error) {
console.error('[Review] Error fetching stats:', error);
return NextResponse.json(
{ error: 'Failed to fetch review stats' },
{ status: 500 }
);
}
}