Files
NoteAI/src/app/api/pages/route.ts
T
2026-03-04 14:04:55 +08:00

152 lines
5.7 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { requireApiAuth } from '@/lib/api-auth';
import { cleanupAccidentalStandaloneInlineCode, markdownToImportHtml } from '@/lib/markdown-import';
import { MAX_TITLE_LENGTH, safeParseTags, normalizeTags } from '@/lib/page-utils';
export async function GET(request: NextRequest) {
const authError = await requireApiAuth();
if (authError) return authError;
try {
const searchParams = request.nextUrl.searchParams;
// lightweight 模式:不返回 content,用于侧边栏等场景
const lightweight = searchParams.get('lightweight') === 'true';
const page = parseInt(searchParams.get('page') || '0', 10);
const limit = parseInt(searchParams.get('limit') || '0', 10);
// 默认行为:返回所有页面(向后兼容)
const usePagination = limit > 0 && limit <= 1000 && page > 0;
const baseSelect = {
id: true,
title: true,
content: !lightweight,
tags: true,
icon: true,
type: true,
parentId: true,
createdAt: true,
updatedAt: true,
order: true,
isLocked: true,
} as const;
if (!usePagination) {
const pages = await prisma.page.findMany({
select: baseSelect,
orderBy: [{ order: 'asc' }, { createdAt: 'desc' }],
});
const parsedPages = pages.map((p) => ({
...p,
content: lightweight ? '' : (p.content || ''),
tags: safeParseTags(p.tags),
}));
return NextResponse.json(parsedPages);
}
// 分页查询
const skip = (page - 1) * limit;
const [pages, total] = await Promise.all([
prisma.page.findMany({
select: baseSelect,
skip,
take: limit,
orderBy: [{ order: 'asc' }, { createdAt: 'desc' }],
}),
prisma.page.count(),
]);
const parsedPages = pages.map((p) => ({
...p,
content: lightweight ? '' : p.content,
tags: safeParseTags(p.tags),
}));
return NextResponse.json({
data: parsedPages,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
});
} catch {
return NextResponse.json({ error: 'Error fetching pages' }, { status: 500 });
}
}
export async function POST(request: Request) {
const authError = await requireApiAuth();
if (authError) return authError;
try {
const body = await request.json().catch(() => null);
if (!body || typeof body !== 'object') {
return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
}
const rawTitle = typeof body.title === 'string' ? body.title.trim() : '';
const title = rawTitle || '无标题';
if (title.length > MAX_TITLE_LENGTH) {
return NextResponse.json({ error: 'Title too long' }, { status: 400 });
}
const type = body.type === 'folder' ? 'folder' : 'file';
const importMarkdown = typeof body.importMarkdown === 'string' ? body.importMarkdown : null;
const rawContent = typeof body.content === 'string' ? body.content : '';
const content = cleanupAccidentalStandaloneInlineCode(
importMarkdown !== null ? markdownToImportHtml(importMarkdown) : rawContent
);
if (importMarkdown !== null && process.env.NODE_ENV !== 'production') {
const lines = importMarkdown.replace(/\r\n?/g, '\n').split('\n');
const fenceLines = lines
.map((line: string, idx: number) => (line.trimStart().startsWith('```') || line.trimStart().startsWith('~~~') ? idx + 1 : -1))
.filter((n: number) => n > 0);
const preEnd = content.indexOf('</code></pre>');
console.info(`[import-md][POST] title="${title}" fences=${fenceLines.join(',') || 'none'} preEnd=${preEnd} len=${content.length}`);
}
const parentId = typeof body.parentId === 'string' ? body.parentId : null;
const tags = normalizeTags(body.tags);
const icon = typeof body.icon === 'string' ? body.icon : null;
const isLocked = typeof body.isLocked === 'boolean' ? body.isLocked : false;
const requestedOrder = Number.isInteger(body.order) ? body.order : undefined;
if (parentId) {
const parent = await prisma.page.findUnique({
where: { id: parentId },
select: { id: true, type: true },
});
if (!parent || parent.type !== 'folder') {
return NextResponse.json({ error: 'Parent folder not found' }, { status: 400 });
}
}
const page = await prisma.page.create({
data: {
title,
content,
tags: JSON.stringify(tags),
parentId,
type,
order: await (async () => {
if (requestedOrder !== undefined) return requestedOrder;
const lastPage = await prisma.page.findFirst({
where: { parentId },
orderBy: { order: 'desc' },
});
return (lastPage?.order ?? -1) + 1;
})(),
icon,
isLocked,
},
});
return NextResponse.json({ ...page, tags: safeParseTags(page.tags) });
} catch {
return NextResponse.json({ error: 'Error creating page' }, { status: 500 });
}
}