6.5 KiB
crm-wiki
Вики (база знаний) для CRM Tvorymo. Открывается в iframe из боковой панели CRM, пользователя узнаёт по токену CRM (postMessage crm:ready → crm:auth).
- Сайт: https://wiki-crm.tes.gd
- Спецификация: 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 |
{ crmOrigins: [...] } — без авторизации |
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 картинки |
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 не ломаются при переименовании и переносе.
Пример (агент или скрипт):
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 https://units.tvorymo.com; … (без X-Frame-Options). Список origin CRM задаётся Crm__Origins (через пробел или запятую): из них принимается crm:auth (плюс ev.source === window.parent) и они же попадают в frame-ancestors. Origin родителя фронтенд определяет по location.ancestorOrigins или document.referrer и шлёт crm:ready только ему; если родитель не определён — каждому origin из списка (браузер доставит лишь совпавшему). Домен стилей CRM — Crm__AssetsOrigin.
На экране ожидания есть диагностика: origin родителя, куда ушёл crm:ready, какие сообщения пришли (без токенов); через 10 секунд без crm:auth — подсказка. Интерфейс на украинском.
Разработка
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__Origins, Crm__AssetsOrigin, Supabase__Url, Supabase__PublishableKey.