feat(API): 对话接口添加用户数据隔离
- 获取对话列表时按用户过滤 - 创建对话时关联当前用户 - 删除对话时验证所有权 - 所有对话操作需要登录认证
This commit is contained in:
parent
bfbeef726d
commit
a7e846d733
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 });
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user