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

View File

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

View File

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