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>
This commit is contained in:
2026-03-31 16:08:12 +02:00
parent 3e22d2e238
commit 16ee9e3beb
33 changed files with 1231 additions and 3404 deletions

View File

@@ -0,0 +1,149 @@
import { getTranslations, setRequestLocale } from 'next-intl/server';
import Link from 'next/link';
import { Header } from '@/components/Header';
import { Footer } from '@/components/Footer';
import { createDb, skillReviewQueries } from '@skillhub/db';
import { getPageAlternates } from '@/lib/seo';
import { getOrSetCache, cacheKeys, cacheTTL } from '@/lib/cache';
import { ShieldAlert, Ban, ExternalLink } from 'lucide-react';
export const dynamic = 'force-dynamic';
async function getMalwareData(page: number, limit: number) {
try {
return await getOrSetCache(cacheKeys.malwarePage(page), cacheTTL.malware, async () => {
const db = createDb();
const offset = (page - 1) * limit;
const [skills, total] = await Promise.all([
skillReviewQueries.getMaliciousSkills(db, limit, offset),
skillReviewQueries.countMaliciousSkills(db),
]);
return { skills, total };
});
} catch (error) {
console.error('Error fetching malware skills:', error);
return { skills: [], total: 0 };
}
}
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return {
alternates: getPageAlternates(locale, '/malware'),
};
}
export default async function MalwarePage({
params,
searchParams,
}: {
params: Promise<{ locale: string }>;
searchParams: Promise<{ page?: string }>;
}) {
const { locale } = await params;
const searchParamsResolved = await searchParams;
setRequestLocale(locale);
const t = await getTranslations('malwarePage');
const tMalicious = await getTranslations('malicious');
const limit = 20;
const page = parseInt(searchParamsResolved.page || '1');
const { skills: malwareSkills } = await getMalwareData(page, limit);
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
{/* Header */}
<section className="section-header bg-gradient-subtle">
<div className="container-main text-center">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-error/10 text-error mb-4">
<ShieldAlert className="w-7 h-7" />
</div>
<h1 className="hero-title mb-4">{t('title')}</h1>
<p className="hero-subtitle max-w-2xl mx-auto">{t('subtitle')}</p>
</div>
</section>
<section className="section bg-surface">
<div className="container-main max-w-4xl">
{malwareSkills.length === 0 ? (
<p className="text-center text-text-muted py-12">{t('noResults')}</p>
) : (
<div className="space-y-4">
{malwareSkills.map((skill) => (
<div
key={skill.id}
className="card p-5 border-error/20 bg-error/[0.02]"
>
<div className="flex flex-col sm:flex-row sm:items-start gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2 flex-wrap">
<Link
href={`/${locale}/skill/${skill.id}`}
className="text-lg font-semibold text-text-primary hover:text-primary-600 transition-colors truncate"
>
{skill.name || skill.id}
</Link>
<span className="flex items-center gap-1 px-1.5 py-0.5 text-xs font-bold rounded border text-error bg-error/10 border-error/20 shrink-0">
<ShieldAlert className="w-3 h-3" />
{tMalicious('badge')}
</span>
</div>
<p className="text-sm text-text-muted mb-2 font-mono">
{skill.githubOwner}/{skill.githubRepo}
</p>
{skill.description && (
<p className="text-sm text-text-secondary line-clamp-2 mb-3">
{skill.description}
</p>
)}
<div className="flex items-center gap-4 text-xs text-text-muted flex-wrap">
<span className="flex items-center gap-1">
<Ban className="w-3.5 h-3.5 text-error" />
{t('downloadBlocked')}
</span>
<span className="flex items-center gap-1">
<Ban className="w-3.5 h-3.5 text-error" />
{t('installBlocked')}
</span>
</div>
</div>
<a
href={`https://github.com/${skill.githubOwner}/${skill.githubRepo}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-sm text-text-muted hover:text-text-primary transition-colors shrink-0"
>
GitHub <ExternalLink className="w-3.5 h-3.5" />
</a>
</div>
</div>
))}
</div>
)}
{/* Back to stats link */}
<div className="text-center mt-8">
<Link
href={`/${locale}/reviewed/stats`}
className="btn-secondary"
>
{locale === 'fa' ? 'بازگشت به آمار بررسی‌ها' : 'Back to Review Statistics'}
</Link>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}