Add admin pinning and user favorites with role management

This commit is contained in:
爱喝水的木子
2026-03-20 13:55:27 +08:00
parent e6788d0e8f
commit 8e6bd210a8
19 changed files with 629 additions and 101 deletions

View File

@@ -0,0 +1,60 @@
import { NextRequest, NextResponse } from "next/server";
import { cookieName, verifySession } from "@/lib/auth";
import { getDb } from "@/lib/mongo";
async function getSessionFromRequest(req: NextRequest) {
const token = req.cookies.get(cookieName)?.value;
return verifySession(token);
}
async function countFavorites(postSlug: string) {
const db = await getDb();
return db.collection("favorites").countDocuments({ postSlug });
}
export async function POST(req: NextRequest, { params }: { params: { slug: string } }) {
const session = await getSessionFromRequest(req);
if (!session?.uid) {
return NextResponse.json({ error: "请先登录后再收藏" }, { status: 401 });
}
const db = await getDb();
const post = await db.collection("posts").findOne({ slug: params.slug }, { projection: { _id: 1 } });
if (!post) {
return NextResponse.json({ error: "内容不存在" }, { status: 404 });
}
await db.collection("favorites").updateOne(
{ ownerId: session.uid, postSlug: params.slug },
{
$setOnInsert: {
ownerId: session.uid,
postSlug: params.slug,
createdAt: new Date().toISOString()
}
},
{ upsert: true }
);
return NextResponse.json({
ok: true,
isFavorited: true,
favoriteCount: await countFavorites(params.slug)
});
}
export async function DELETE(req: NextRequest, { params }: { params: { slug: string } }) {
const session = await getSessionFromRequest(req);
if (!session?.uid) {
return NextResponse.json({ error: "请先登录后再取消收藏" }, { status: 401 });
}
const db = await getDb();
await db.collection("favorites").deleteOne({ ownerId: session.uid, postSlug: params.slug });
return NextResponse.json({
ok: true,
isFavorited: false,
favoriteCount: await countFavorites(params.slug)
});
}

View File

@@ -0,0 +1,55 @@
import { NextRequest, NextResponse } from "next/server";
import { cookieName, verifySession } from "@/lib/auth";
import { getDb } from "@/lib/mongo";
import { canPinPost } from "@/lib/posts";
async function getSessionFromRequest(req: NextRequest) {
const token = req.cookies.get(cookieName)?.value;
return verifySession(token);
}
async function getPost(slug: string) {
const db = await getDb();
const post = await db.collection("posts").findOne({ slug });
return { db, post };
}
export async function POST(req: NextRequest, { params }: { params: { slug: string } }) {
const session = await getSessionFromRequest(req);
const { db, post } = await getPost(params.slug);
if (!post) {
return NextResponse.json({ error: "内容不存在" }, { status: 404 });
}
if (!canPinPost(post, session)) {
return NextResponse.json({ error: "只有管理员可以置顶内容" }, { status: 403 });
}
const now = new Date().toISOString();
await db.collection("posts").updateOne(
{ _id: post._id },
{ $set: { isPinned: true, pinnedAt: now, updatedAt: now } }
);
return NextResponse.json({ ok: true, isPinned: true, pinnedAt: now });
}
export async function DELETE(req: NextRequest, { params }: { params: { slug: string } }) {
const session = await getSessionFromRequest(req);
const { db, post } = await getPost(params.slug);
if (!post) {
return NextResponse.json({ error: "内容不存在" }, { status: 404 });
}
if (!canPinPost(post, session)) {
return NextResponse.json({ error: "只有管理员可以取消置顶" }, { status: 403 });
}
const now = new Date().toISOString();
await db.collection("posts").updateOne(
{ _id: post._id },
{ $set: { isPinned: false, updatedAt: now }, $unset: { pinnedAt: "" } }
);
return NextResponse.json({ ok: true, isPinned: false });
}

View File

@@ -84,5 +84,6 @@ export async function DELETE(req: NextRequest, { params }: { params: { slug: str
}
await db.collection("posts").deleteOne({ _id: existingPost._id });
await db.collection("favorites").deleteMany({ postSlug: params.slug });
return NextResponse.json({ ok: true });
}

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { cookieName, verifySession } from "@/lib/auth";
import { getDb } from "@/lib/mongo";
import { DEFAULT_OPC_SIGNAL, OPC_SIGNAL_VALUES } from "@/lib/opc";
import { buildPinnedSort, serializePost } from "@/lib/posts";
import { generateSlug } from "@/lib/slug";
import { findUserById, getEffectiveDailyPostLimit, getShanghaiDayRange } from "@/lib/users";
@@ -19,17 +20,11 @@ export async function GET() {
const posts = await db
.collection("posts")
.find({}, { projection: { markdown: 0 } })
.sort({ createdAt: -1 })
.sort(buildPinnedSort())
.limit(50)
.toArray();
return NextResponse.json(
posts.map((post) => ({
...post,
author: post.author ?? "匿名",
_id: post._id?.toString()
}))
);
return NextResponse.json(posts.map((post) => serializePost(post)));
}
export async function POST(req: NextRequest) {
@@ -85,7 +80,8 @@ export async function POST(req: NextRequest) {
slug,
createdAt: now,
updatedAt: now,
views: 0
views: 0,
isPinned: false
});
return NextResponse.json({ ok: true, slug, todayCount: todayCount + 1, limit });