# Export & Utility

### Health Check

```
GET /health                          → { "status": "ok" }
```

Public, no auth. Also available at `/api/v1/health`.

### Export

Convert markdown to PDF or DOCX via pandoc:

```
POST /export
```

```json
{
  "content": "# My Document\n\nBody text...",
  "format": "pdf|docx",
  "filename": "my-document"
}
```

Returns the binary file with appropriate Content-Type. Requires
`pandoc` in the container (available in the unified and backend
Docker images).

---

## 17b. Files & Storage

All binary content in Chat Switchboard flows through a single
**ObjectStore** abstraction backed by PVC (filesystem) or S3. All
file metadata lives in one `files` table. The old `attachments`
table is dropped.

### Storage Backend

| Operation | Description |
|-----------|-------------|
| `Put(key, reader, size, contentType)` | Store a blob |
| `Get(key)` → reader, size, contentType | Retrieve a blob |
| `Delete(key)` | Remove a blob |
| `DeletePrefix(prefix)` | Bulk remove (channel deletion) |
| `Exists(key)` | Check without reading |
| `Healthy()` | Backend health check |
| `Stats()` | Aggregate metrics |

Backends: `pvc` (`STORAGE_PATH=/data/storage`) or `s3`
(`S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`).

### Key Namespacing

| Prefix | Pattern | What |
|--------|---------|------|
| `attachments/` | `attachments/{channel_id}/{file_id}_{filename}` | User uploads + AI outputs (legacy prefix retained for existing blobs) |
| `kb/` | `kb/{kb_id}/{doc_id}_{filename}` | Knowledge base documents |
| `exports/` | `exports/{user_id}/{export_id}_{filename}` | Chat/data exports |
| `workspace/` | `workspace/{workspace_id}/{path}` | Workspace files |

### File Object

```sql
CREATE TABLE files (
    id              UUID PRIMARY KEY,
    channel_id      UUID REFERENCES channels(id) ON DELETE CASCADE,
    message_id      UUID REFERENCES messages(id) ON DELETE SET NULL,
    user_id         UUID REFERENCES users(id) ON DELETE SET NULL,
    project_id      UUID REFERENCES projects(id) ON DELETE SET NULL,
    origin          TEXT NOT NULL DEFAULT 'user_upload'
                    CHECK (origin IN ('user_upload','tool_output','system')),
    filename        TEXT NOT NULL,
    content_type    TEXT NOT NULL DEFAULT 'application/octet-stream',
    size_bytes      BIGINT NOT NULL DEFAULT 0,
    storage_key     TEXT NOT NULL,
    display_hint    TEXT NOT NULL DEFAULT 'download'
                    CHECK (display_hint IN ('inline','download','thumbnail')),
    extracted_text  TEXT,
    metadata        JSONB DEFAULT '{}',
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW()
);
```

**Origin:**

| Value | Who creates | Examples |
|-------|-------------|---------|
| `user_upload` | User via upload UI | PDF, image, doc attached to message |
| `tool_output` | Tool execution during completion | DALL-E image, video gen, code artifact |
| `system` | Platform | Export, avatar stored as file |

**Display hint:**

| Value | Frontend behavior |
|-------|-------------------|
| `inline` | Render in message stream (images, video, audio, HTML) |
| `download` | Filename + size + download button |
| `thumbnail` | Thumbnail preview, click to expand |

### Tool Output Flow

1. Tool executes (image gen, video render, etc.)
2. Tool writes blob via `ObjectStore.Put`
3. Tool creates file record: `POST /files` with `origin: "tool_output"`, `message_id` = current assistant message
4. Tool result includes `file_id` reference
5. WebSocket emits `file.created` event:

```json
{
  "event": "file.created",
  "channel_id": "uuid",
  "message_id": "uuid",
  "file": { ...file object... }
}
```

6. Frontend renders inline based on `display_hint` + `content_type`

The event fires during streaming so the user sees the artifact
before the completion finishes.

### Content Type → Display Hint Defaults

| Content Type | Default Hint | Rendering |
|-------------|-------------|-----------|
| `image/*` | `inline` | `<img>` in message, lightbox on click |
| `video/*` | `inline` | `<video>` player |
| `audio/*` | `inline` | `<audio>` player |
| `text/html` | `inline` | Sandboxed iframe |
| `application/pdf` | `thumbnail` | Thumb + PDF viewer on click |
| `text/*`, `application/json` | `inline` | Syntax-highlighted code block |
| Everything else | `download` | Filename + download button |

Tools can override the default hint explicitly.

### File Manager (Future Surface)

A user-scoped file browser backed by `GET /files?page=1&per_page=50`.
Displays all files owned by the user across all channels, grouped by
channel or date. Supports download and delete. Deleting a file that
is referenced by a message replaces the inline render with a
"file deleted" placeholder.
