nyk
feat: phase 1 workspace isolation across auth and core APIs (#112)
4e0867d unverified
Raw
History Blame Contribute Delete
3.73 kB
import { NextRequest, NextResponse } from 'next/server'
import { getDatabase } from '@/lib/db'
import { requireRole } from '@/lib/auth'
import { logger } from '@/lib/logger'
/**
* GET /api/chat/conversations - List conversations derived from messages
* Query params: agent (filter by participant), limit, offset
*/
export async function GET(request: NextRequest) {
const auth = requireRole(request, 'viewer')
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
try {
const db = getDatabase()
const { searchParams } = new URL(request.url)
const workspaceId = auth.user.workspace_id ?? 1
const agent = searchParams.get('agent')
const limit = Math.min(parseInt(searchParams.get('limit') || '50'), 200)
const offset = parseInt(searchParams.get('offset') || '0')
let query: string
const params: any[] = []
if (agent) {
// Get conversations where this agent is a participant
query = `
SELECT
m.conversation_id,
MAX(m.created_at) as last_message_at,
COUNT(*) as message_count,
COUNT(DISTINCT m.from_agent) + COUNT(DISTINCT CASE WHEN m.to_agent IS NOT NULL THEN m.to_agent END) as participant_count,
SUM(CASE WHEN m.to_agent = ? AND m.read_at IS NULL THEN 1 ELSE 0 END) as unread_count
FROM messages m
WHERE m.workspace_id = ? AND (m.from_agent = ? OR m.to_agent = ? OR m.to_agent IS NULL)
GROUP BY m.conversation_id
ORDER BY last_message_at DESC
LIMIT ? OFFSET ?
`
params.push(agent, workspaceId, agent, agent, limit, offset)
} else {
query = `
SELECT
m.conversation_id,
MAX(m.created_at) as last_message_at,
COUNT(*) as message_count,
COUNT(DISTINCT m.from_agent) + COUNT(DISTINCT CASE WHEN m.to_agent IS NOT NULL THEN m.to_agent END) as participant_count,
0 as unread_count
FROM messages m
WHERE m.workspace_id = ?
GROUP BY m.conversation_id
ORDER BY last_message_at DESC
LIMIT ? OFFSET ?
`
params.push(workspaceId, limit, offset)
}
const conversations = db.prepare(query).all(...params) as any[]
// Prepare last message statement once (avoids N+1)
const lastMsgStmt = db.prepare(`
SELECT * FROM messages
WHERE conversation_id = ? AND workspace_id = ?
ORDER BY created_at DESC
LIMIT 1
`);
const withLastMessage = conversations.map((conv) => {
const lastMsg = lastMsgStmt.get(conv.conversation_id, workspaceId) as any;
return {
...conv,
last_message: lastMsg
? {
...lastMsg,
metadata: lastMsg.metadata ? JSON.parse(lastMsg.metadata) : null
}
: null
}
})
// Get total count for pagination
let countQuery: string
const countParams: any[] = [workspaceId]
if (agent) {
countQuery = `
SELECT COUNT(DISTINCT m.conversation_id) as total
FROM messages m
WHERE m.workspace_id = ? AND (m.from_agent = ? OR m.to_agent = ? OR m.to_agent IS NULL)
`
countParams.push(agent, agent)
} else {
countQuery = 'SELECT COUNT(DISTINCT conversation_id) as total FROM messages WHERE workspace_id = ?'
}
const countRow = db.prepare(countQuery).get(...countParams) as { total: number }
return NextResponse.json({ conversations: withLastMessage, total: countRow.total, page: Math.floor(offset / limit) + 1, limit })
} catch (error) {
logger.error({ err: error }, 'GET /api/chat/conversations error')
return NextResponse.json({ error: 'Failed to fetch conversations' }, { status: 500 })
}
}