# 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 ` | | `X-Wiki-Workspace` | `shared` (по умолчанию), `personal:` или `` своего workspace | Сервер проверяет токен: `exp`, `aud = "authenticated"` и `GET /auth/v1/user` с publishable-ключом (результат кэшируется на 5 минут по SHA-256 токена). Workspace из запроса не доверяется: `shared` доступен всем, `personal:` только владельцу `uid`, произвольный `` проверяется запросом `GET /rest/v1/workspaces?id=eq.` с токеном пользователя (RLS Supabase). Ответы: `401` — токен не принят или истёк, `403` — нет доступа к workspace. Пока CRM не передаёт workspace (задача Олега `mv6p5b92slw1an`), фронтенд работает в `shared`. Когда в `crm:auth` появится `workspace: { id, kind, title }`, он подхватится автоматически: `kind: "shared"` → `shared`, `kind: "personal"` → `personal:`, иначе `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/)`, картинка: `![alt](/api/images/)`. Ссылки по 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`.