diff --git a/app/api/tasks/[taskId]/client-logs/route.ts b/app/api/tasks/[taskId]/client-logs/route.ts new file mode 100644 index 00000000..7fe979f2 --- /dev/null +++ b/app/api/tasks/[taskId]/client-logs/route.ts @@ -0,0 +1,66 @@ +import { NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { db } from '@/lib/db/client' +import * as schema from '@/lib/db/schema' +import { eq, and, isNull } from 'drizzle-orm' +import { getServerSession } from '@/lib/session/get-server-session' +import { redactSensitiveInfo } from '@/lib/utils/logging' + +const { tasks, logEntrySchema } = schema + +// Schema for the request body +const clientLogsSchema = z.object({ + logs: z.array(logEntrySchema), +}) + +/** + * POST /api/tasks/[taskId]/client-logs + * Append client-side logs to the task's log database + */ +export async function POST(request: NextRequest, { params }: { params: Promise<{ taskId: string }> }) { + try { + const session = await getServerSession() + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const { taskId } = await params + + // Parse the request body + const body = await request.json() + const { logs } = clientLogsSchema.parse(body) + + if (!logs || logs.length === 0) { + return NextResponse.json({ error: 'No logs provided' }, { status: 400 }) + } + + // Get the task and verify ownership (exclude soft-deleted) + const [task] = await db + .select() + .from(tasks) + .where(and(eq(tasks.id, taskId), eq(tasks.userId, session.user.id), isNull(tasks.deletedAt))) + .limit(1) + + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }) + } + + // Redact sensitive information from all log messages + const sanitizedLogs = logs.map((log) => ({ + ...log, + message: redactSensitiveInfo(log.message), + timestamp: log.timestamp || new Date(), + })) + + // Append the client logs to the existing logs + const existingLogs = task.logs || [] + const updatedLogs = [...existingLogs, ...sanitizedLogs] + + await db.update(tasks).set({ logs: updatedLogs, updatedAt: new Date() }).where(eq(tasks.id, taskId)) + + return NextResponse.json({ success: true }) + } catch (error) { + console.error('Error appending client logs:', error) + return NextResponse.json({ error: 'Failed to append client logs' }, { status: 500 }) + } +} diff --git a/components/logs-pane.tsx b/components/logs-pane.tsx index e036417c..7da47487 100644 --- a/components/logs-pane.tsx +++ b/components/logs-pane.tsx @@ -17,7 +17,7 @@ interface LogsPaneProps { } type TabType = 'logs' | 'terminal' -type LogFilterType = 'all' | 'platform' | 'server' +type LogFilterType = 'all' | 'platform' | 'server' | 'client' export function LogsPane({ task, onHeightChange }: LogsPaneProps) { const [copiedLogs, setCopiedLogs] = useState(false) @@ -161,8 +161,10 @@ export function LogsPane({ task, onHeightChange }: LogsPaneProps) { const getFilteredLogs = (filter: LogFilterType) => { return (task.logs || []).filter((log) => { const isServerLog = log.message.startsWith('[SERVER]') + const isClientLog = log.message.startsWith('[CLIENT]') if (filter === 'server') return isServerLog - if (filter === 'platform') return !isServerLog + if (filter === 'client') return isClientLog + if (filter === 'platform') return !isServerLog && !isClientLog return true }) } @@ -302,6 +304,7 @@ export function LogsPane({ task, onHeightChange }: LogsPaneProps) { All Platform Server + Client