feat(API): 对话接口添加用户数据隔离

- 获取对话列表时按用户过滤
- 创建对话时关联当前用户
- 删除对话时验证所有权
- 所有对话操作需要登录认证
This commit is contained in:
gaoziman 2025-12-19 22:36:56 +08:00
parent bfbeef726d
commit a7e846d733
3 changed files with 172 additions and 39 deletions

View File

@ -1,7 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/drizzle/db';
import { conversations, messages } from '@/drizzle/schema';
import { eq, asc } from 'drizzle-orm';
import { eq, asc, and } from 'drizzle-orm';
import { getCurrentUser } from '@/lib/auth';
interface RouteParams {
params: Promise<{ id: string }>;
@ -10,10 +11,22 @@ interface RouteParams {
// GET /api/conversations/[id] - 获取单个对话及其消息
export async function GET(request: Request, { params }: RouteParams) {
try {
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
const { id } = await params;
const conversation = await db.query.conversations.findFirst({
where: eq(conversations.conversationId, id),
where: and(
eq(conversations.conversationId, id),
eq(conversations.userId, user.userId)
),
});
if (!conversation) {
@ -44,10 +57,34 @@ export async function GET(request: Request, { params }: RouteParams) {
// PUT /api/conversations/[id] - 更新对话
export async function PUT(request: Request, { params }: RouteParams) {
try {
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
const { id } = await params;
const body = await request.json();
const { title, isPinned, isArchived } = body;
// 验证对话属于当前用户
const existingConversation = await db.query.conversations.findFirst({
where: and(
eq(conversations.conversationId, id),
eq(conversations.userId, user.userId)
),
});
if (!existingConversation) {
return NextResponse.json(
{ error: 'Conversation not found' },
{ status: 404 }
);
}
const updateData: Record<string, unknown> = {
updatedAt: new Date(),
};
@ -70,13 +107,6 @@ export async function PUT(request: Request, { params }: RouteParams) {
.where(eq(conversations.conversationId, id))
.returning();
if (!updated) {
return NextResponse.json(
{ error: 'Conversation not found' },
{ status: 404 }
);
}
return NextResponse.json(updated);
} catch (error) {
console.error('Failed to update conversation:', error);
@ -90,24 +120,40 @@ export async function PUT(request: Request, { params }: RouteParams) {
// DELETE /api/conversations/[id] - 删除对话
export async function DELETE(request: Request, { params }: RouteParams) {
try {
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
const { id } = await params;
// 先删除相关消息
await db.delete(messages).where(eq(messages.conversationId, id));
// 验证对话属于当前用户
const existingConversation = await db.query.conversations.findFirst({
where: and(
eq(conversations.conversationId, id),
eq(conversations.userId, user.userId)
),
});
// 再删除对话
const [deleted] = await db
.delete(conversations)
.where(eq(conversations.conversationId, id))
.returning();
if (!deleted) {
if (!existingConversation) {
return NextResponse.json(
{ error: 'Conversation not found' },
{ status: 404 }
);
}
// 先删除相关消息
await db.delete(messages).where(eq(messages.conversationId, id));
// 再删除对话
await db
.delete(conversations)
.where(eq(conversations.conversationId, id));
return NextResponse.json({ success: true });
} catch (error) {
console.error('Failed to delete conversation:', error);

View File

@ -1,24 +1,48 @@
import { NextResponse } from 'next/server';
import { db } from '@/drizzle/db';
import { conversations, messages } from '@/drizzle/schema';
import { sql } from 'drizzle-orm';
import { sql, eq, inArray } from 'drizzle-orm';
import { getCurrentUser } from '@/lib/auth';
// GET /api/conversations/all - 获取统计信息
// GET /api/conversations/all - 获取当前用户的统计信息
export async function GET() {
try {
// 获取对话数量
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
// 获取当前用户的对话数量
const conversationCount = await db
.select({ count: sql<number>`count(*)` })
.from(conversations);
.from(conversations)
.where(eq(conversations.userId, user.userId));
// 获取消息数量
const messageCount = await db
.select({ count: sql<number>`count(*)` })
.from(messages);
// 获取当前用户的对话 ID 列表
const userConversations = await db
.select({ conversationId: conversations.conversationId })
.from(conversations)
.where(eq(conversations.userId, user.userId));
const conversationIds = userConversations.map(c => c.conversationId);
// 获取当前用户的消息数量
let messageCountValue = 0;
if (conversationIds.length > 0) {
const messageCount = await db
.select({ count: sql<number>`count(*)` })
.from(messages)
.where(inArray(messages.conversationId, conversationIds));
messageCountValue = Number(messageCount[0]?.count || 0);
}
return NextResponse.json({
conversationCount: Number(conversationCount[0]?.count || 0),
messageCount: Number(messageCount[0]?.count || 0),
messageCount: messageCountValue,
});
} catch (error) {
console.error('Failed to get stats:', error);
@ -29,18 +53,37 @@ export async function GET() {
}
}
// DELETE /api/conversations/all - 清除所有对话和消息
// DELETE /api/conversations/all - 清除当前用户的所有对话和消息
export async function DELETE() {
try {
// 先删除所有消息
await db.delete(messages);
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
// 再删除所有对话
await db.delete(conversations);
// 获取当前用户的所有对话 ID
const userConversations = await db
.select({ conversationId: conversations.conversationId })
.from(conversations)
.where(eq(conversations.userId, user.userId));
const conversationIds = userConversations.map(c => c.conversationId);
if (conversationIds.length > 0) {
// 删除当前用户对话相关的所有消息
await db.delete(messages).where(inArray(messages.conversationId, conversationIds));
// 删除当前用户的所有对话
await db.delete(conversations).where(eq(conversations.userId, user.userId));
}
return NextResponse.json({
success: true,
message: 'All conversations and messages have been deleted',
message: 'All your conversations and messages have been deleted',
});
} catch (error) {
console.error('Failed to delete all conversations:', error);

View File

@ -1,14 +1,27 @@
import { NextResponse } from 'next/server';
import { db } from '@/drizzle/db';
import { conversations, messages } from '@/drizzle/schema';
import { desc, eq } from 'drizzle-orm';
import { desc, eq, and } from 'drizzle-orm';
import { nanoid } from 'nanoid';
import { getCurrentUser } from '@/lib/auth';
// GET /api/conversations - 获取对话列表
// GET /api/conversations - 获取当前用户的对话列表
export async function GET() {
try {
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
const conversationList = await db.query.conversations.findMany({
where: eq(conversations.isArchived, false),
where: and(
eq(conversations.isArchived, false),
eq(conversations.userId, user.userId)
),
orderBy: [desc(conversations.lastMessageAt)],
});
@ -25,6 +38,15 @@ export async function GET() {
// POST /api/conversations - 创建新对话
export async function POST(request: Request) {
try {
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
const body = await request.json();
const { title, model, tools, enableThinking } = body;
@ -38,6 +60,7 @@ export async function POST(request: Request) {
model: model || 'claude-sonnet-4-20250514',
tools: tools || [],
enableThinking: enableThinking || false,
userId: user.userId, // 关联当前用户
})
.returning();
@ -54,6 +77,15 @@ export async function POST(request: Request) {
// DELETE /api/conversations - 批量删除对话
export async function DELETE(request: Request) {
try {
// 获取当前用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
);
}
const body = await request.json();
const { conversationIds } = body;
@ -64,10 +96,22 @@ export async function DELETE(request: Request) {
);
}
// 删除相关消息
// 删除相关消息和对话(只删除属于当前用户的)
for (const id of conversationIds) {
await db.delete(messages).where(eq(messages.conversationId, id));
await db.delete(conversations).where(eq(conversations.conversationId, id));
// 先验证对话属于当前用户
const [conv] = await db
.select()
.from(conversations)
.where(and(
eq(conversations.conversationId, id),
eq(conversations.userId, user.userId)
))
.limit(1);
if (conv) {
await db.delete(messages).where(eq(messages.conversationId, id));
await db.delete(conversations).where(eq(conversations.conversationId, id));
}
}
return NextResponse.json({ success: true });