import { cookies } from "next/headers"; import { AdminPostList } from "@/components/AdminPostList"; import { AdminUserManager } from "@/components/AdminUserManager"; import { CreatePostForm } from "@/components/CreatePostForm"; import { SocialLinksManager } from "@/components/SocialLinksManager"; import { cookieName, isAdminSession, verifySession } from "@/lib/auth"; import { getDb } from "@/lib/mongo"; import { buildOwnedPostFilter, buildPinnedSort, serializePost } from "@/lib/posts"; import { getDefaultSocialLinks, getSiteSettings } from "@/lib/site-settings"; import { findUserById, getEffectiveDailyPostLimit, getShanghaiDayRange } from "@/lib/users"; import { Post } from "@/types/post"; export const dynamic = "force-dynamic"; type ManagedUser = { id: string; username: string; displayName: string; role: "user" | "sponsor" | "admin"; dailyPostLimit: number; postCount: number; todayPostCount: number; posts: Array<{ slug: string; title: string; createdAt: string; isPinned?: boolean }>; }; const ROLE_LABELS: Record = { user: "普通", sponsor: "赞助", admin: "管理员" }; async function fetchRecentPosts(session: Awaited>) { const db = await getDb(); const posts = await db .collection("posts") .find(buildOwnedPostFilter(session), { projection: { markdown: 0 } }) .sort(buildPinnedSort()) .limit(20) .toArray(); return posts.map((post: any) => ({ ...serializePost(post), createdAtText: new Date(post.createdAt).toLocaleString("zh-CN", { hour12: false, timeZone: "Asia/Shanghai" }) })); } async function fetchFavoritePosts(session: Awaited>): Promise { if (!session?.uid) { return []; } const db = await getDb(); const favorites = await db .collection("favorites") .find({ ownerId: session.uid }, { projection: { postSlug: 1, createdAt: 1 } }) .sort({ createdAt: -1 }) .limit(20) .toArray(); const slugs = favorites.map((item: any) => item.postSlug).filter(Boolean); if (slugs.length === 0) { return []; } const posts = await db .collection("posts") .find({ slug: { $in: slugs } }, { projection: { markdown: 0 } }) .toArray(); const postMap = new Map(posts.map((post: any) => [post.slug, serializePost(post)])); return slugs.map((slug) => postMap.get(slug)).filter(Boolean) as Post[]; } async function fetchAvailableTags(session: Awaited>) { const db = await getDb(); const tags = await db .collection("posts") .aggregate([ { $match: buildOwnedPostFilter(session) }, { $unwind: "$tags" }, { $group: { _id: "$tags", count: { $sum: 1 } } }, { $sort: { count: -1, _id: 1 } } ]) .toArray(); return tags.map((item: any) => item._id).filter(Boolean); } async function fetchPublishQuota(session: Awaited>) { const user = await findUserById(session?.uid); const limit = getEffectiveDailyPostLimit(user || undefined); const { startIso, endIso } = getShanghaiDayRange(); const todayCount = session?.uid ? await getDb().then((db) => db.collection("posts").countDocuments({ ownerId: session.uid, createdAt: { $gte: startIso, $lt: endIso } }) ) : 0; return { limit, todayCount }; } async function fetchManagedUsers(): Promise { const db = await getDb(); const { startIso, endIso } = getShanghaiDayRange(); const users = await db .collection("users") .find( {}, { projection: { username: 1, displayName: 1, role: 1, dailyPostLimit: 1 } } ) .sort({ createdAt: 1 }) .toArray(); const posts = await db .collection("posts") .find( {}, { projection: { slug: 1, title: 1, createdAt: 1, ownerId: 1, author: 1, isPinned: 1 } } ) .sort(buildPinnedSort()) .toArray(); const authorToUserId = new Map(); users.forEach((user: any) => { const id = user._id?.toString(); if (!id) return; if (user.username) authorToUserId.set(String(user.username).trim().toLowerCase(), id); if (user.displayName) authorToUserId.set(String(user.displayName).trim().toLowerCase(), id); }); const postCountMap = new Map(); const todayCountMap = new Map(); const postsByOwner = new Map< string, Array<{ slug: string; title: string; createdAt: string; isPinned?: boolean }> >(); posts.forEach((post: any) => { const resolvedOwnerId = post.ownerId || authorToUserId.get(String(post.author || "").trim().toLowerCase()); if (!resolvedOwnerId) return; const list = postsByOwner.get(resolvedOwnerId) ?? []; list.push({ slug: post.slug, title: post.title ?? "未命名", createdAt: post.createdAt ?? new Date().toISOString(), isPinned: Boolean(post.isPinned) }); postsByOwner.set(resolvedOwnerId, list); postCountMap.set(resolvedOwnerId, (postCountMap.get(resolvedOwnerId) ?? 0) + 1); if (post.createdAt >= startIso && post.createdAt < endIso) { todayCountMap.set(resolvedOwnerId, (todayCountMap.get(resolvedOwnerId) ?? 0) + 1); } }); return users .map((user: any) => { const id = user._id?.toString(); if (!id) return null; return { id, username: user.username ?? "", displayName: user.displayName ?? user.username ?? "", role: user.role === "admin" || user.role === "sponsor" || user.role === "user" ? user.role : "user", dailyPostLimit: getEffectiveDailyPostLimit(user), postCount: postCountMap.get(id) ?? 0, todayPostCount: todayCountMap.get(id) ?? 0, posts: postsByOwner.get(id) ?? [] }; }) .filter(Boolean) as ManagedUser[]; } export default async function AdminPage() { const token = cookies().get(cookieName)?.value; const session = await verifySession(token); const adminView = isAdminSession(session); const roleLabel = ROLE_LABELS[(session?.role as ManagedUser["role"]) || "user"]; const [recentPosts, favoritePosts, availableTags, publishQuota, managedUsers, siteSettings] = await Promise.all([ fetchRecentPosts(session), fetchFavoritePosts(session), fetchAvailableTags(session), fetchPublishQuota(session), adminView ? fetchManagedUsers() : Promise.resolve([] as ManagedUser[]), adminView ? getSiteSettings() : Promise.resolve({ socialLinks: getDefaultSocialLinks() }) ]); return (

内容后台

登录用户可以发布、编辑自己的内容和管理自己的收藏;管理员额外拥有置顶、删帖、删用户、调整用户等级/额度,以及维护首页社交链接的全部权限。

{session?.name || "未登录"} · {roleLabel} 查看统计
{adminView ? : null} {adminView ? : null}
); }