74 lines
5.6 KiB
Markdown
74 lines
5.6 KiB
Markdown
# crm-wiki
|
||
|
||
Вики (база знаний) для CRM Tvorymo. Открывается в iframe из боковой панели CRM, пользователя узнаёт по токену CRM (postMessage `crm:ready` → `crm:auth`).
|
||
|
||
- Сайт: https://wiki-crm.tes.gd
|
||
- Спецификация: [SPEC.md](SPEC.md)
|
||
- Стек: ASP.NET Core 10 (minimal API) + SQLite (FTS5), фронтенд Vite + React + TipTap.
|
||
- Данные: `/data/wiki.db` и `/data/images/` на постоянном томе `wiki-data` (Coolify).
|
||
|
||
## Авторизация
|
||
|
||
Каждый запрос к `/api/*` (кроме `/api/verify` и `/api/config`) требует:
|
||
|
||
| Заголовок | Значение |
|
||
|---|---|
|
||
| `Authorization` | `Bearer <access_token Supabase из CRM>` |
|
||
| `X-Wiki-Workspace` | `shared` (по умолчанию), `personal:<uid>` или `<uuid>` своего workspace |
|
||
|
||
Сервер проверяет токен: `exp`, `aud = "authenticated"` и `GET <SUPABASE_URL>/auth/v1/user` с publishable-ключом (результат кэшируется на 5 минут по SHA-256 токена). Workspace из запроса не доверяется: `shared` доступен всем, `personal:<uid>` только владельцу `uid`, произвольный `<uuid>` проверяется запросом `GET /rest/v1/workspaces?id=eq.<uuid>` с токеном пользователя (RLS Supabase). Ответы: `401` — токен не принят или истёк, `403` — нет доступа к workspace.
|
||
|
||
Пока CRM не передаёт workspace (задача Олега `mv6p5b92slw1an`), фронтенд работает в `shared`. Когда в `crm:auth` появится `workspace: { id, kind, title }`, он подхватится автоматически: `kind: "shared"` → `shared`, `kind: "personal"` → `personal:<uid>`, иначе `id`. Сообщение о смене workspace тоже обрабатывается (`{ type: "crm:workspace", workspace: {…} }`), если Олег выберет другое имя — поправить в `frontend/src/crm.ts`.
|
||
|
||
## REST API
|
||
|
||
Все ответы JSON (кроме `/markdown` и картинок). Ошибки: `{ "error": "…" }`.
|
||
|
||
| Метод | Путь | Что делает |
|
||
|---|---|---|
|
||
| `GET` | `/health` | `{ status, dbCreatedAt }` — без авторизации; `dbCreatedAt` не меняется, пока жив том с базой |
|
||
| `GET` | `/api/config` | `{ crmOrigin }` — без авторизации |
|
||
| `POST` | `/api/verify` | `{ access_token }` → `{ ok, id, email, name, exp }` — без авторизации |
|
||
| `GET` | `/api/me` | текущий пользователь и workspace |
|
||
| `GET` | `/api/tree` | `{ workspace, nodes: [Node] }` — все папки и страницы workspace без текста |
|
||
| `GET` | `/api/nodes/{id}` | папка или страница (Page) |
|
||
| `GET` | `/api/pages/{id}` | страница (Page), `404` для папки |
|
||
| `GET` | `/api/pages/{id}/markdown` | текст страницы, `text/markdown` |
|
||
| `POST` | `/api/pages` | `{ title, parentId?, markdown? }` → `201` Page |
|
||
| `POST` | `/api/folders` | `{ title, parentId? }` → `201` Page (kind = folder) |
|
||
| `PUT` | `/api/pages/{id}` | `{ title?, markdown?, version }` → Page; если `version` устарела — `409 { error, current }` |
|
||
| `PATCH` | `/api/nodes/{id}` | переименовать/переместить: `{ title?, parentId?, version? }`; `parentId: null` — в корень. Перенос в себя/потомка или в страницу — `400` |
|
||
| `DELETE` | `/api/nodes/{id}` | удалить узел со всем содержимым → `{ deleted: N }` |
|
||
| `GET` | `/api/search?q=…&limit=30` | `{ results: [{ id, kind, title, snippet, path }] }` — FTS5 по названию и тексту, префиксный поиск по словам; совпадения в `snippet` обрамлены `\u0002…\u0003` |
|
||
| `POST` | `/api/images` | `multipart/form-data`: `file` (PNG/JPEG/GIF/WebP, до 10 МБ), `pageId?` → `201 { id, url }` |
|
||
| `GET` | `/api/images/{id}` | картинка; нужен токен и доступ к workspace картинки |
|
||
|
||
```ts
|
||
type Node = { id, parentId: string|null, kind: "folder"|"page", title, version: number,
|
||
createdAt, updatedAt, updatedBy /* uid */, updatedByName };
|
||
type Page = Node & { markdown: string, path: { id, kind, title }[] /* папки от корня */ };
|
||
```
|
||
|
||
Формат текста — Markdown (GFM: таблицы, списки задач). Ссылка на другую страницу: `[текст](/p/<id>)`, картинка: ``. Ссылки по id не ломаются при переименовании и переносе.
|
||
|
||
Пример (агент или скрипт):
|
||
|
||
```sh
|
||
curl -H "Authorization: Bearer $TOKEN" https://wiki-crm.tes.gd/api/tree
|
||
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
|
||
-d '{"title":"Регламент","markdown":"# Привет"}' https://wiki-crm.tes.gd/api/pages
|
||
```
|
||
|
||
## Заголовки
|
||
|
||
`Content-Security-Policy: frame-ancestors https://test.units.tvorymo.com; …` (без `X-Frame-Options`). Origin CRM задаётся `Crm__Origin`, домен стилей CRM — `Crm__AssetsOrigin`.
|
||
|
||
## Разработка
|
||
|
||
```sh
|
||
cd backend/Wiki.Api && ASPNETCORE_URLS=http://127.0.0.1:5080 dotnet run # база в ./data
|
||
cd frontend && npm i && npm run dev # Vite, /api проксируется на 127.0.0.1:5080
|
||
```
|
||
|
||
Настройки (env): `Storage__DataDir`, `Crm__Origin`, `Crm__AssetsOrigin`, `Supabase__Url`, `Supabase__PublishableKey`.
|