34 lines
1.7 KiB
TypeScript
34 lines
1.7 KiB
TypeScript
'use server'
|
|
|
|
import { noteRepository } from '@/entities/note'
|
|
import { audit, db, logger, serverErrors } from '@/shared/api'
|
|
import { exportToS3 } from '@/shared/lib/optional'
|
|
import { ensureVisitor } from '@/shared/lib/visitor-session'
|
|
|
|
export type ExportResult = { ok: true; key: string; size: number } | { ok: false; message: string }
|
|
|
|
/**
|
|
* Writes the visitor's notes to the S3 bucket as one JSON object (S3 module).
|
|
*
|
|
* The caller (a `useActionState` form) needs the object key/size or the error to show
|
|
* the visitor, and the write needs an audit event like every other action here — before
|
|
* this, the button gave no feedback at all and nothing landed in the audit timeline
|
|
* (stage sweep, 2026-10-11). The `ExportResult | null` / `FormData` signature is
|
|
* `useActionState`'s action shape, not a plain server action.
|
|
*/
|
|
export async function exportNotes(_state: ExportResult | null, _form: FormData): Promise<ExportResult> {
|
|
const actor = await ensureVisitor()
|
|
const notes = await noteRepository(db()).listByAuthor(actor)
|
|
try {
|
|
const key = await exportToS3(actor, notes)
|
|
logger()?.info('notes.exported', { 'actor.id': actor, 'export.count': notes.length, outcome: key ? 'ok' : 'skipped' })
|
|
if (!key) return { ok: false, message: 'S3 is not linked; nothing was exported.' }
|
|
await audit().log('notes.exported', { actor: { kind: 'human', id: actor }, target: { id: key } })
|
|
return { ok: true, key, size: Buffer.byteLength(JSON.stringify(notes)) }
|
|
} catch (error) {
|
|
logger()?.recordException(error, { 'conusai.action': 'notes.exported', 'actor.id': actor })
|
|
serverErrors().captureException(error)
|
|
return { ok: false, message: error instanceof Error ? error.message : 'the export failed' }
|
|
}
|
|
}
|