113 lines
3.7 KiB
Markdown
113 lines
3.7 KiB
Markdown
# Canvas Service Architecture
|
|
|
|
## Overview
|
|
Canvas is an HTML tool showcase platform at `canvas.ephron.ren`. Unlike Blog (markdown files) and Prompt (SQLite), Canvas uses a **file-based storage** system.
|
|
|
|
## Storage Structure
|
|
```
|
|
content/pages/
|
|
├── meta.json # Metadata for all pages
|
|
├── slug-1.html # HTML content files
|
|
├── slug-2.html
|
|
└── .gitkeep
|
|
```
|
|
|
|
- Each canvas is a standalone HTML file (filename = slug)
|
|
- `meta.json` stores all metadata in a single JSON file
|
|
- No database involved
|
|
|
|
## meta.json Format
|
|
```json
|
|
{
|
|
"pages": {
|
|
"hermes-agent-ai": {
|
|
"title": "Hermes Agent — 自我进化的 AI 智能体",
|
|
"description": "介绍页",
|
|
"source": "other",
|
|
"category": "tool",
|
|
"tags": ["AI", "Agent"],
|
|
"draft": false,
|
|
"created_at": "2026-05-06T00:00:00",
|
|
"updated_at": "2026-05-06T00:00:00",
|
|
"created_by": "svc_xxx",
|
|
"updated_by": "svc_xxx",
|
|
"ownership_type": "service",
|
|
"handoff_to_human": false,
|
|
"views": 42
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Valid Categories (hardcoded)
|
|
```python
|
|
CANVAS_CATEGORIES = [
|
|
("tool", "🔧 实用工具"),
|
|
("game", "🎮 小游戏"),
|
|
("visual", "🎨 可视化"),
|
|
("learning", "📚 学习教育"),
|
|
("productivity", "⚡ 效率提升"),
|
|
("fun", "🎉 趣味娱乐"),
|
|
("other", "📦 其他"),
|
|
]
|
|
```
|
|
|
|
Source: `canvas/src/services/canvas.py` lines 624-632.
|
|
|
|
## Route Structure
|
|
| Route file | Auth | Purpose |
|
|
|------------|------|---------|
|
|
| `pages.py` | None (public) | Homepage list, view page, raw HTML |
|
|
| `service_api.py` | Bearer Token | Draft CRUD only |
|
|
| `admin.py` | Cookie (ephron_auth) | Full CRUD + publish toggle |
|
|
|
|
## Key Endpoints
|
|
|
|
### Public (no auth)
|
|
- `GET /` — Homepage, shows non-draft canvases grouped by category
|
|
- `GET /view/{slug}` — View page with iframe embedding `/raw/{slug}`
|
|
- `GET /raw/{slug}` — Raw HTML content (iframe src target)
|
|
|
|
### Service API (Bearer Token)
|
|
- `GET /api/service/canvas` — List own drafts only
|
|
- `GET /api/service/canvas/{slug}` — Get own draft
|
|
- `POST /api/service/canvas` — Create draft
|
|
- `PATCH /api/service/canvas/{slug}` — Update own draft (fails if published)
|
|
- `DELETE /api/service/canvas/{slug}` — Delete own draft
|
|
|
|
### Admin (Cookie auth)
|
|
- `GET /admin` — Admin dashboard with all canvases (incl. drafts)
|
|
- `GET /admin/new` — New canvas form
|
|
- `POST /admin/new` — Create canvas
|
|
- `GET /admin/edit/{slug}` — Edit form
|
|
- `POST /admin/edit/{slug}` — Save edits
|
|
- `POST /admin/toggle-draft` — Toggle draft status (publish/unpublish)
|
|
- `POST /admin/delete` — Delete canvas
|
|
|
|
## Auth Flow (Admin)
|
|
1. Login at `auth.ephron.ren/api/login` with form data (`username` + `password`)
|
|
2. Response sets `ephron_auth` cookie on `.ephron.ren` domain
|
|
3. Admin routes check cookie via `is_authenticated()`
|
|
4. CSRF token required for all POST forms (generated per-request)
|
|
|
|
## iframe Embedding Issue
|
|
The `/view/{slug}` page uses `<iframe src="/raw/{slug}">` to display canvas content.
|
|
The shared `security_headers.py` middleware blocks iframe embedding with `X-Frame-Options: DENY` and `frame-ancestors 'none'`.
|
|
|
|
**Solution**: Override headers in the `/raw/{slug}` route handler:
|
|
```python
|
|
headers={
|
|
"X-Frame-Options": "SAMEORIGIN",
|
|
"Content-Security-Policy": "...frame-ancestors 'self'...",
|
|
}
|
|
```
|
|
|
|
## Source Files
|
|
- `canvas/src/routes/pages.py` — Public page routes
|
|
- `canvas/src/routes/service_api.py` — Service API (Bearer Token)
|
|
- `canvas/src/routes/admin.py` — Admin routes (Cookie auth)
|
|
- `canvas/src/services/canvas.py` — Core service (storage, CRUD, categories)
|
|
- `canvas/src/services/auth.py` — Auth helpers
|
|
- `canvas/src/config.py` — Config (CONTENT_DIR, COOKIE_NAME, etc.)
|
|
- `shared/security_headers.py` — Shared security middleware
|