From 8daac94f5a08c2226bc0d6a25023df41c7566b4c Mon Sep 17 00:00:00 2001 From: hermes Date: Wed, 17 Jun 2026 04:32:29 +0000 Subject: [PATCH] feat: admin article management, sources, settings, rules, and scheduler - Add article list, editor, and deletion UI. - Add source management with bias/credibility/active toggles. - Add encrypted API key CRUD UI. - Add automated search rules with query, sources, schedule presets, and per-rule AI model override. - Implement node-cron scheduler and instrumentation hook. - Add admin settings including password change and quota reference. --- src/app/admin/articles/[id]/page.tsx | 167 +++++ src/app/admin/articles/page.tsx | 175 ++++++ src/app/admin/keys/page.tsx | 179 ++++++ src/app/admin/rules/page.tsx | 602 +++++++++++++++++++ src/app/admin/settings/page.tsx | 150 +++++ src/app/admin/sources/page.tsx | 98 +++ src/app/api/admin/articles/[id]/route.ts | 72 +++ src/app/api/admin/articles/route.ts | 37 ++ src/app/api/admin/keys/[id]/route.ts | 37 ++ src/app/api/admin/keys/route.ts | 49 ++ src/app/api/admin/rules/[id]/route.ts | 45 ++ src/app/api/admin/rules/[id]/run/route.ts | 35 ++ src/app/api/admin/rules/[id]/runs/route.ts | 22 + src/app/api/admin/rules/route.ts | 70 +++ src/app/api/admin/settings/password/route.ts | 32 + src/app/api/admin/sources/[id]/route.ts | 32 + src/app/api/admin/sources/route.ts | 10 + src/instrumentation.ts | 6 + src/lib/rules.ts | 71 +++ src/lib/scheduler.ts | 40 ++ 20 files changed, 1929 insertions(+) create mode 100644 src/app/admin/articles/[id]/page.tsx create mode 100644 src/app/admin/articles/page.tsx create mode 100644 src/app/admin/keys/page.tsx create mode 100644 src/app/admin/rules/page.tsx create mode 100644 src/app/admin/settings/page.tsx create mode 100644 src/app/admin/sources/page.tsx create mode 100644 src/app/api/admin/articles/[id]/route.ts create mode 100644 src/app/api/admin/articles/route.ts create mode 100644 src/app/api/admin/keys/[id]/route.ts create mode 100644 src/app/api/admin/keys/route.ts create mode 100644 src/app/api/admin/rules/[id]/route.ts create mode 100644 src/app/api/admin/rules/[id]/run/route.ts create mode 100644 src/app/api/admin/rules/[id]/runs/route.ts create mode 100644 src/app/api/admin/rules/route.ts create mode 100644 src/app/api/admin/settings/password/route.ts create mode 100644 src/app/api/admin/sources/[id]/route.ts create mode 100644 src/app/api/admin/sources/route.ts create mode 100644 src/instrumentation.ts create mode 100644 src/lib/rules.ts create mode 100644 src/lib/scheduler.ts diff --git a/src/app/admin/articles/[id]/page.tsx b/src/app/admin/articles/[id]/page.tsx new file mode 100644 index 0000000..4a8620b --- /dev/null +++ b/src/app/admin/articles/[id]/page.tsx @@ -0,0 +1,167 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { ArticleStatus, Bias } from '@prisma/client'; + +type Article = { + id: string; + title: string; + summary: string | null; + url: string; + primaryBias: Bias; + politicalTags: string; + rankScore: number; + credibilityScore: number; + status: ArticleStatus; + topics: string; + aiExplanation: string | null; +}; + +export default function EditArticlePage() { + const { id } = useParams<{ id: string }>(); + const router = useRouter(); + const [article, setArticle] = useState
(null); + const [saving, setSaving] = useState(false); + + useEffect(() => { + fetch(`/api/admin/articles/${id}`) + .then((res) => res.json()) + .then((data) => { + setArticle(data); + }); + }, [id]); + + async function handleSave(e: React.FormEvent) { + e.preventDefault(); + if (!article) return; + setSaving(true); + + const res = await fetch(`/api/admin/articles/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: article.title, + summary: article.summary, + primaryBias: article.primaryBias, + politicalTags: JSON.parse(article.politicalTags || '[]'), + rankScore: article.rankScore, + credibilityScore: article.credibilityScore, + status: article.status, + topics: JSON.parse(article.topics || '[]'), + }), + }); + + setSaving(false); + if (res.ok) { + router.push('/admin/articles'); + } else { + alert('Failed to save'); + } + } + + if (!article) return

Loading...

; + + return ( +
+

Edit Article

+
+
+ + setArticle({ ...article, title: e.target.value })} + className="w-full px-3 py-2 border rounded-lg dark:bg-neutral-800 dark:border-neutral-700" + /> +
+
+ +