Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions apps/web/src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

import type { ClinicalNoteRequest } from "@note-core"
import { createClinicalNoteText } from "@note-core"
import { isHostedMode } from "@storage/hosted-mode"
import { getAnthropicApiKey } from "@storage/server-api-keys"
import { writeAuditEntry } from "@storage/audit-log"
import { writeServerAuditEntry } from "@storage/server-audit"

export async function generateClinicalNote(params: ClinicalNoteRequest): Promise<string> {
if (isHostedMode()) {
throw new Error("generateClinicalNote server action is disabled in hosted mode. Use /api/notes/generate instead.")
}

const apiKey = getAnthropicApiKey()

try {
// Audit log: note generation started
await writeAuditEntry({
await writeServerAuditEntry({
event_type: "note.generation_started",
success: true,
metadata: {
Expand All @@ -22,7 +27,7 @@ export async function generateClinicalNote(params: ClinicalNoteRequest): Promise
const result = await createClinicalNoteText({ ...params, apiKey })

// Audit log: note generated successfully
await writeAuditEntry({
await writeServerAuditEntry({
event_type: "note.generated",
success: true,
metadata: {
Expand All @@ -34,7 +39,7 @@ export async function generateClinicalNote(params: ClinicalNoteRequest): Promise
return result
} catch (error) {
// Audit log: note generation failed
await writeAuditEntry({
await writeServerAuditEntry({
event_type: "note.generation_failed",
success: false,
error_message: error instanceof Error ? error.message : String(error),
Expand Down
80 changes: 80 additions & 0 deletions apps/web/src/app/api/notes/generate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { NextRequest } from 'next/server'
import { createClinicalNoteText, type ClinicalNoteRequest } from '@note-core'
import { getAnthropicApiKey } from '@storage/server-api-keys'
import { writeServerAuditEntry, logSanitizedServerError } from '@storage/server-audit'
import { requireAuth } from '@/lib/auth'

export const runtime = 'nodejs'

function jsonError(status: number, code: string, message: string) {
return new Response(JSON.stringify({ error: { code, message } }), {
status,
headers: { 'Content-Type': 'application/json' },
})
}

function isClinicalNoteRequest(value: unknown): value is ClinicalNoteRequest {
if (!value || typeof value !== 'object') return false
const payload = value as Record<string, unknown>
return typeof payload.transcript === 'string'
}

export async function POST(req: NextRequest) {
const auth = await requireAuth(req)
if (!auth) {
return jsonError(401, 'unauthorized', 'Authentication required')
}

try {
const body = await req.json()
if (!isClinicalNoteRequest(body)) {
return jsonError(400, 'validation_error', 'Invalid clinical note request payload')
}

const apiKey = getAnthropicApiKey()

await writeServerAuditEntry({
event_type: 'note.generation_started',
success: true,
org_id: auth.orgId,
user_id: auth.userId,
metadata: {
template: body.template || 'default',
transcript_length: body.transcript.length,
},
})

const note = await createClinicalNoteText({
...body,
apiKey,
})

await writeServerAuditEntry({
event_type: 'note.generated',
success: true,
org_id: auth.orgId,
user_id: auth.userId,
metadata: {
template: body.template || 'default',
note_length: note.length,
},
})

return new Response(JSON.stringify({ note }), {
headers: { 'Content-Type': 'application/json' },
})
} catch (error) {
logSanitizedServerError('notes.generate', error)

await writeServerAuditEntry({
event_type: 'note.generation_failed',
success: false,
org_id: auth.orgId,
user_id: auth.userId,
error_code: 'note_generation_failed',
error_message: error instanceof Error ? error.message : String(error),
})

return jsonError(500, 'note_generation_failed', 'Failed to generate clinical note')
}
}