f357330ba8
- 将用户角色字段从role改为is_admin布尔值 - 更新相关API权限检查逻辑 - 修改数据库schema和迁移文件 - 调整前端用户显示逻辑 - 添加API响应工具函数 - 优化权限检查中间件 - 重构英雄组件为原子组件
137 lines
3.5 KiB
TypeScript
137 lines
3.5 KiB
TypeScript
import { NextRequest } from 'next/server';
|
|
import { db } from '@/db';
|
|
import { content } from '@/db/schema';
|
|
import { checkIsAdmin, getAdminUserId } from '@/lib/auth/check-permission';
|
|
import { createAuditLog } from '@/lib/audit';
|
|
import { forbidden, badRequest, success, handleApiError, validationError } from '@/lib/api-response';
|
|
import { eq, desc, and, like, sql } from 'drizzle-orm';
|
|
import { nanoid } from 'nanoid';
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const { isAdmin } = await checkIsAdmin();
|
|
|
|
if (!isAdmin) {
|
|
return forbidden();
|
|
}
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const type = searchParams.get('type');
|
|
const status = searchParams.get('status');
|
|
const search = searchParams.get('search');
|
|
const page = parseInt(searchParams.get('page') || '1');
|
|
const limit = parseInt(searchParams.get('limit') || '20');
|
|
const offset = (page - 1) * limit;
|
|
|
|
const conditions = [];
|
|
|
|
if (type) {
|
|
conditions.push(eq(content.type, type as 'news' | 'product' | 'service' | 'case'));
|
|
}
|
|
|
|
if (status) {
|
|
conditions.push(eq(content.status, status as 'draft' | 'published' | 'archived'));
|
|
}
|
|
|
|
if (search) {
|
|
conditions.push(like(content.title, `%${search}%`));
|
|
}
|
|
|
|
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
|
|
const [items, countResult] = await Promise.all([
|
|
db
|
|
.select()
|
|
.from(content)
|
|
.where(whereClause)
|
|
.orderBy(desc(content.createdAt))
|
|
.limit(limit)
|
|
.offset(offset),
|
|
db
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(content)
|
|
.where(whereClause),
|
|
]);
|
|
|
|
const total = countResult[0]?.count || 0;
|
|
|
|
return success({
|
|
items,
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
total,
|
|
totalPages: Math.ceil(total / limit),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const { isAdmin } = await checkIsAdmin();
|
|
const userId = await getAdminUserId();
|
|
|
|
if (!isAdmin || !userId) {
|
|
return forbidden();
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { type, title, slug, excerpt, contentBody, coverImage, category, tags, status: contentStatus, metadata } = body;
|
|
|
|
if (!type || !title || !slug) {
|
|
return validationError('缺少必要字段', { required: ['type', 'title', 'slug'] });
|
|
}
|
|
|
|
const existingContent = await db
|
|
.select()
|
|
.from(content)
|
|
.where(eq(content.slug, slug))
|
|
.limit(1);
|
|
|
|
if (existingContent.length > 0) {
|
|
return badRequest('Slug 已存在');
|
|
}
|
|
|
|
const now = new Date();
|
|
const newContent = await db
|
|
.insert(content)
|
|
.values({
|
|
id: nanoid(),
|
|
type,
|
|
title,
|
|
slug,
|
|
excerpt: excerpt || null,
|
|
content: contentBody || '',
|
|
coverImage: coverImage || null,
|
|
category: category || null,
|
|
tags: tags || [],
|
|
status: contentStatus || 'draft',
|
|
publishedAt: contentStatus === 'published' ? now : null,
|
|
authorId: userId,
|
|
metadata: metadata || null,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
.returning();
|
|
|
|
await createAuditLog({
|
|
userId,
|
|
action: 'create',
|
|
resourceType: 'content',
|
|
resourceId: newContent[0]!.id,
|
|
details: {
|
|
type,
|
|
title,
|
|
status: contentStatus || 'draft',
|
|
},
|
|
});
|
|
|
|
return success(newContent[0], 201);
|
|
} catch (error) {
|
|
return handleApiError(error);
|
|
}
|
|
}
|