2026-07-19 01:12:16 +08:00
import { redirect } from "next/navigation" ;
import { getSession } from "@/lib/auth" ;
import { db } from "@/lib/db" ;
export const dynamic = "force-dynamic" ;
2026-07-19 03:58:03 +08:00
export default async function AdminPage ({ searchParams } : { searchParams : Promise < { updated? : string ; error? : string } > }) {
const user = await getSession (); if ( ! user || user . role !== "admin" ) redirect ( "/" ); const query = await searchParams ;
const failures = db . prepare ( "SELECT j.id,j.kind,j.trigger,j.status,j.attempts,j.last_error,j.created_at,j.finished_at,s.id AS source_id,s.name FROM sync_jobs j JOIN sources s ON s.id=j.source_id WHERE j.status='failed' OR s.sync_status='error' ORDER BY COALESCE(j.finished_at,j.created_at) DESC LIMIT 100" ). all () as any [];
const reports = db . prepare ( "SELECT r.id,r.reason,r.created_at,p.id AS post_id,p.content,u.username FROM reports r JOIN posts p ON p.id=r.post_id LEFT JOIN users u ON u.id=r.reporter_id WHERE r.resolved=0 ORDER BY r.created_at LIMIT 100" ). all () as any [];
const users = db . prepare ( "SELECT id,username,role,disabled,created_at FROM users ORDER BY created_at DESC LIMIT 100" ). all () as any [];
const errors = db . prepare ( "SELECT scope,message,created_at FROM error_events ORDER BY id DESC LIMIT 30" ). all () as any [];
2026-07-19 05:51:08 +08:00
const audits = db . prepare ( "SELECT a.action,a.target_type,a.target_id,a.metadata_json,a.created_at,u.username FROM audit_events a LEFT JOIN users u ON u.id=a.actor_user_id ORDER BY a.id DESC LIMIT 50" ). all () as any [];
2026-07-19 03:58:03 +08:00
const sources = db . prepare ( "SELECT s.id,s.name,s.base_url,s.sync_status,s.last_synced_at,s.is_enabled,count(sm.user_id) AS member_count FROM sources s LEFT JOIN source_members sm ON sm.source_id=s.id GROUP BY s.id ORDER BY s.id DESC" ). all () as any [];
return <>< h1 > 管理員 </ h1 >{ query . updated && < p > 管理操作已完成。 </ p >}{ query . error && < p className = "error" > 管理操作未完成。 </ p >}
< section className = "card" >< h2 > 待審核檢舉 </ h2 >{ reports . length ? < ul className = "job-list" >{ reports . map (( report ) => < li key = { report . id }>< strong > 貼文 # { report . post_id }</ strong > · 檢舉者 @ { report . username || "已刪除使用者" }< br />{ report . reason }< br />< span className = "meta" >{ report . content . slice ( 0 , 180 )} · { new Date ( report . created_at + "Z" ). toLocaleString ( "zh-TW" )}</ span >< div className = "row" >< form action = "/api/admin/moderate" method = "post" >< input type = "hidden" name = "action" value = "hide-post" />< input type = "hidden" name = "id" value = { report . post_id } />< button className = "danger" > 隱藏貼文並結案 </ button ></ form >< form action = "/api/admin/moderate" method = "post" >< input type = "hidden" name = "action" value = "resolve-report" />< input type = "hidden" name = "id" value = { report . id } />< button > 保留貼文並結案 </ button ></ form ></ div ></ li >)}</ ul > : < p className = "muted" > 沒有待審核檢舉。 </ p >}</ section >
< section className = "card" >< h2 > 使用者 </ h2 >< ul className = "job-list" >{ users . map (( account ) => < li key = { account . id }>< strong > @ { account . username }</ strong > · < span className = "tag" >{ account . role }</ span > · { account . disabled ? "已停權" : "正常" }< div className = "row" >{ account . id !== user . id && < form action = "/api/admin/moderate" method = "post" >< input type = "hidden" name = "action" value = { account . disabled ? "enable-user" : "disable-user" } />< input type = "hidden" name = "id" value = { account . id } />< button className = { account . disabled ? "" : "danger" }>{ account . disabled ? "解除停權" : "停權" }</ button ></ form >}< form action = "/api/admin/moderate" method = "post" >< input type = "hidden" name = "action" value = "reset-password" />< input type = "hidden" name = "id" value = { account . id } />< input name = "password" type = "password" minLength = { 10 } required placeholder = "管理員重設密碼" />< button > 重設密碼 </ button ></ form ></ div ></ li >)}</ ul ></ section >
< section className = "card" >< h2 > 失敗或異常工作 </ h2 >{ failures . length ? < ul className = "job-list" >{ failures . map (( item ) => < li key = { ` ${ item . id } - ${ item . source_id } ` }>< strong >{ item . name }</ strong > (來源 # { item . source_id } ) · { item . kind || "source" } · < span className = "tag" >{ item . status || "error" }</ span >< br />< span className = "error" >{ item . last_error || "來源處於錯誤狀態" }</ span ></ li >)}</ ul > : < p className = "muted" > 沒有同步異常。 </ p >}</ section >
< section className = "card" >< h2 > 最近系統錯誤 </ h2 >{ errors . length ? < ul className = "job-list" >{ errors . map (( error , index ) => < li key = { index }>< strong >{ error . scope }</ strong > · < span className = "error" >{ error . message }</ span >< br />< span className = "meta" >{ new Date ( error . created_at + "Z" ). toLocaleString ( "zh-TW" )}</ span ></ li >)}</ ul > : < p className = "muted" > 尚無記錄。 </ p >}</ section >
2026-07-19 05:51:08 +08:00
< section className = "card" >< h2 > 稽核紀錄 </ h2 >{ audits . length ? < ul className = "job-list" >{ audits . map (( item , index ) => < li key = { index }>< strong >{ item . action }</ strong > · @ { item . username || "system" } · { item . target_type } # { item . target_id || "—" }< br />< span className = "meta" >{ new Date ( item . created_at + "Z" ). toLocaleString ( "zh-TW" )}</ span ></ li >)}</ ul > : < p className = "muted" > 尚無稽核紀錄。 </ p >}</ section >
2026-07-19 03:58:03 +08:00
< section className = "card" >< h2 > 所有來源 </ h2 >< ul className = "job-list" >{ sources . map (( source ) => < li key = { source . id }>< strong >{ source . name }</ strong > · < span className = "tag" >{ source . is_enabled ? source . sync_status : "disabled" }</ span > · 成員 { source . member_count }< br />< span className = "meta" > # { source . id } · { source . base_url } · 上次同步: { source . last_synced_at || "尚未完成" }</ span ></ li >)}</ ul ></ section >
</>;
2026-07-19 01:12:16 +08:00
}