Files
solo-company-feed/app/api/posts/route.ts
2026-03-20 13:55:27 +08:00

89 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextRequest, NextResponse } from "next/server";
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";
const postSchema = z.object({
title: z.string().min(2).max(80),
markdown: z.string().min(5),
cover: z.string().url().optional(),
tags: z.array(z.string().trim()).optional(),
signal: z.enum(OPC_SIGNAL_VALUES).default(DEFAULT_OPC_SIGNAL)
});
export async function GET() {
const db = await getDb();
const posts = await db
.collection("posts")
.find({}, { projection: { markdown: 0 } })
.sort(buildPinnedSort())
.limit(50)
.toArray();
return NextResponse.json(posts.map((post) => serializePost(post)));
}
export async function POST(req: NextRequest) {
const json = await req.json().catch(() => ({}));
const parsed = postSchema.safeParse(json);
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
}
const token = req.cookies.get(cookieName)?.value;
const session = await verifySession(token);
if (!session?.uid) {
return NextResponse.json({ error: "请先登录后再发布" }, { status: 401 });
}
const user = await findUserById(session.uid);
if (!user) {
return NextResponse.json({ error: "用户不存在或已被删除" }, { status: 401 });
}
const limit = getEffectiveDailyPostLimit(user);
const { startIso, endIso } = getShanghaiDayRange();
const db = await getDb();
const todayCount = await db.collection("posts").countDocuments({
ownerId: session.uid,
createdAt: { $gte: startIso, $lt: endIso }
});
if (todayCount >= limit) {
return NextResponse.json({ error: `今日发布数量已达到上限(${limit}` }, { status: 429 });
}
const data = parsed.data;
const author = session.name ?? "匿名";
const now = new Date().toISOString();
let slug = generateSlug();
for (let attempt = 0; attempt < 5; attempt += 1) {
const exists = await db.collection("posts").findOne({ slug });
if (!exists) break;
slug = generateSlug();
}
const existsAfter = await db.collection("posts").findOne({ slug });
if (existsAfter) {
return NextResponse.json({ error: "生成内容标识失败" }, { status: 500 });
}
await db.collection("posts").insertOne({
...data,
author,
ownerId: session.uid,
slug,
createdAt: now,
updatedAt: now,
views: 0,
isPinned: false
});
return NextResponse.json({ ok: true, slug, todayCount: todayCount + 1, limit });
}