import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { neo4jQueryContract } from '@/lib/api/contracts/tools/databases/neo4j' import { parseToolRequest } from '@/lib/api/server' import { checkInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { convertNeo4jTypesToJSON, createNeo4jDriver, validateCypherQuery, } from '@/app/api/tools/neo4j/utils' const logger = createLogger('Neo4jQueryAPI') export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateId().slice(0, 8) let driver = null let session = null const auth = await checkInternalAuth(request) if (!auth.success || !auth.userId) { logger.warn(`[${requestId}] Unauthorized Neo4j query attempt`) return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) } try { const parsed = await parseToolRequest(neo4jQueryContract, request, { logger }) if (!parsed.success) return parsed.response const params = parsed.data.body logger.info( `[${requestId}] Executing Neo4j query on ${params.host}:${params.port}/${params.database}` ) const validation = validateCypherQuery(params.cypherQuery) if (!validation.isValid) { logger.warn(`[${requestId}] Cypher query validation failed: ${validation.error}`) return NextResponse.json( { error: `Query validation failed: ${validation.error}` }, { status: 400 } ) } driver = await createNeo4jDriver({ host: params.host, port: params.port, database: params.database, username: params.username, password: params.password, encryption: params.encryption, }) session = driver.session({ database: params.database }) const result = await session.run(params.cypherQuery, params.parameters) const records = result.records.map((record) => { const obj: Record = {} record.keys.forEach((key) => { if (typeof key === 'string') { obj[key] = convertNeo4jTypesToJSON(record.get(key)) } }) return obj }) const summary = { resultAvailableAfter: result.summary.resultAvailableAfter.toNumber(), resultConsumedAfter: result.summary.resultConsumedAfter.toNumber(), counters: { nodesCreated: result.summary.counters.updates().nodesCreated, nodesDeleted: result.summary.counters.updates().nodesDeleted, relationshipsCreated: result.summary.counters.updates().relationshipsCreated, relationshipsDeleted: result.summary.counters.updates().relationshipsDeleted, propertiesSet: result.summary.counters.updates().propertiesSet, labelsAdded: result.summary.counters.updates().labelsAdded, labelsRemoved: result.summary.counters.updates().labelsRemoved, indexesAdded: result.summary.counters.updates().indexesAdded, indexesRemoved: result.summary.counters.updates().indexesRemoved, constraintsAdded: result.summary.counters.updates().constraintsAdded, constraintsRemoved: result.summary.counters.updates().constraintsRemoved, }, } logger.info(`[${requestId}] Query executed successfully, returned ${records.length} records`) return NextResponse.json({ message: `Found ${records.length} records`, records, recordCount: records.length, summary, }) } catch (error) { const errorMessage = getErrorMessage(error, 'Unknown error occurred') logger.error(`[${requestId}] Neo4j query failed:`, error) return NextResponse.json({ error: `Neo4j query failed: ${errorMessage}` }, { status: 500 }) } finally { if (session) { await session.close() } if (driver) { await driver.close() } } })