export type DocBlock = | { type: 'paragraph'; text: string } | { type: 'list'; items: string[] } | { type: 'code'; language: string; code: string; title?: string } | { type: 'callout'; variant: 'info' | 'warning' | 'tip'; title: string; text: string } | { type: 'table'; headers: string[]; rows: string[][] } | { type: 'oauth-examples' } | { type: 'auth-login-examples' } | { type: 'auth-ldap-examples' } | { type: 'api-reference' }; export interface DocSection { id: string; title: string; blocks: DocBlock[]; } export interface DocPage { slug: string; title: string; description: string; sections: DocSection[]; } export const docPages: DocPage[] = [ { slug: 'getting-started', title: 'Начало работы', description: 'Обзор экосистемы Identity Provider, порты сервисов и быстрый локальный запуск.', sections: [ { id: 'overview', title: 'Обзор', blocks: [ { type: 'paragraph', text: 'Lendry ID — enterprise Identity Provider в стиле Yandex ID: единый аккаунт, OAuth 2.0, LDAP/LDAPS, семейные группы, чат, PIN-блокировка сессий и админ-панель. Монорепозиторий состоит из NestJS-микросервисов, Go-сервисов и Next.js-приложений.' }, { type: 'callout', variant: 'info', title: 'Домен', text: 'Production-домен: id.lendry.ru. Локально API доступен на порту 3000, frontend — 3002, документация — 3003.' } ] }, { id: 'requirements', title: 'Требования', blocks: [ { type: 'list', items: [ 'Node.js 20.19+ и npm 10+', 'Docker и Docker Compose', 'PostgreSQL 16, Redis 7, RabbitMQ 3.13, MinIO', 'Go 1.23+ (для локальной сборки media-ws и ldap-auth)' ] } ] }, { id: 'quick-start', title: 'Быстрый запуск', blocks: [ { type: 'code', language: 'bash', title: 'Локально через Docker', code: `git clone lendry-id cd lendry-id docker compose up -d --build # Проверка curl http://localhost:3000/health curl http://localhost:3003/docs/getting-started` }, { type: 'table', headers: ['Сервис', 'Порт', 'Назначение'], rows: [ ['api-gateway', '3000', 'REST API, Swagger'], ['sso-core', '3001 / 50051', 'Бизнес-логика, gRPC'], ['frontend', '3002', 'UI входа и профиля'], ['docs', '3003', 'Документация'], ['media-ws', '8085', 'WebSocket уведомлений и чата'], ['ldap-auth', '8086', 'LDAP/LDAPS аутентификация'], ['MinIO', '9000 / 9001', 'Хранилище медиа'], ['RabbitMQ', '5672 / 15672', 'Очереди событий'] ] } ] }, { id: 'first-user', title: 'Первый пользователь', blocks: [ { type: 'paragraph', text: 'Первый зарегистрированный пользователь автоматически получает права супер-администратора (isSuperAdmin). Race condition защищена Redis-lock и Serializable-транзакцией PostgreSQL.' }, { type: 'code', language: 'bash', title: 'Регистрация через API', code: `curl -X POST http://localhost:3000/auth/register \\ -H "Content-Type: application/json" \\ -d '{"displayName":"Администратор","email":"admin@example.com","password":"SecurePass123"}'` } ] } ] }, { slug: 'architecture', title: 'Архитектура', description: 'Схема микросервисов, потоки данных и технологический стек.', sections: [ { id: 'stack', title: 'Технологический стек', blocks: [ { type: 'table', headers: ['Компонент', 'Технологии'], rows: [ ['sso-core', 'NestJS, Prisma 7+, PostgreSQL, gRPC'], ['api-gateway', 'NestJS, REST, Swagger'], ['frontend / docs', 'Next.js App Router, React, Tailwind, shadcn/ui'], ['media-ws', 'Go, Gorilla WebSocket, Redis, RabbitMQ'], ['ldap-auth', 'Go, go-ldap'], ['Инфраструктура', 'Docker, Redis, RabbitMQ, MinIO'] ] } ] }, { id: 'flow', title: 'Поток запросов', blocks: [ { type: 'paragraph', text: 'Клиент (браузер или стороннее приложение) обращается к api-gateway по REST. Gateway проксирует вызовы в sso-core через gRPC. Медиафайлы загружаются в MinIO через presigned URL. Realtime-события (уведомления, чат) доставляются через media-ws по WebSocket с JWT-авторизацией.' }, { type: 'list', items: [ 'JWT access + refresh tokens для сессий', 'PIN-код: при включении сессия создаётся с pinVerified=false', 'SystemSetting — динамические бизнес-правила без хардкода во frontend', 'LinkedAccount — привязка LDAP, Google, Yandex и других провайдеров' ] } ] } ] }, { slug: 'deployment', title: 'Развёртывание на сервере', description: 'Пошаговый гайд по production-развёртыванию на Linux-сервере с Docker, Nginx и TLS.', sections: [ { id: 'server-requirements', title: '1. Требования к серверу', blocks: [ { type: 'list', items: [ 'Ubuntu 22.04+ / Debian 12+ / аналогичный Linux', 'Минимум 4 GB RAM, 2 vCPU, 40 GB SSD', 'Домен с DNS A-записью (например id.lendry.ru)', 'Открытые порты 80 и 443 (остальное — только localhost / internal network)' ] } ] }, { id: 'install-docker', title: '2. Установка Docker', blocks: [ { type: 'code', language: 'bash', code: `sudo apt update && sudo apt install -y ca-certificates curl gnupg curl -fsSL https://get.docker.com | sudo sh sudo usermod -aG docker $USER docker compose version` } ] }, { id: 'clone-config', title: '3. Клонирование и переменные окружения', blocks: [ { type: 'paragraph', text: 'Создайте файл .env в корне проекта с production-секретами. Никогда не коммитьте его в git.' }, { type: 'code', language: 'bash', title: '.env (пример)', code: `# Секреты JWT и шифрования — сгенерируйте: openssl rand -base64 48 JWT_ACCESS_SECRET=your-long-access-secret JWT_REFRESH_SECRET=your-long-refresh-secret DATA_ENCRYPTION_KEY=your-32-byte-encryption-key-change-me # PostgreSQL (можно оставить docker internal) POSTGRES_USER=lendry POSTGRES_PASSWORD=strong-db-password POSTGRES_DB=lendry_id # RabbitMQ RABBITMQ_DEFAULT_USER=lendry RABBITMQ_DEFAULT_PASS=strong-rmq-password # MinIO MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=strong-minio-password` }, { type: 'callout', variant: 'warning', title: 'PUBLIC_API_URL', text: 'В production задайте PUBLIC_API_URL=https://id.lendry.ru для sso-core и api-gateway, а также MINIO_PUBLIC_ENDPOINT=id.lendry.ru (или CDN-домен для MinIO).' } ] }, { id: 'docker-compose-prod', title: '4. Production docker-compose', blocks: [ { type: 'paragraph', text: 'Для production рекомендуется не публиковать PostgreSQL, Redis и RabbitMQ наружу — оставьте только api-gateway (3000), frontend (3002) и docs (3003) за reverse proxy.' }, { type: 'code', language: 'bash', code: `export DOCKER_BUILDKIT=1 docker compose up -d --build # Проверка health всех сервисов docker compose ps docker compose logs -f sso-core api-gateway` } ] }, { id: 'nginx', title: '5. Nginx + TLS (Let\'s Encrypt)', blocks: [ { type: 'code', language: 'nginx', title: '/etc/nginx/sites-available/id.lendry.ru', code: `server { listen 443 ssl http2; server_name id.lendry.ru; ssl_certificate /etc/letsencrypt/live/id.lendry.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/id.lendry.ru/privkey.pem; # REST API + Swagger location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Frontend UI location /app/ { proxy_pass http://127.0.0.1:3002/; proxy_set_header Host $host; } # WebSocket location /ws { proxy_pass http://127.0.0.1:8085; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } } # Документация на docs.id.lendry.ru или /docs-proxy server { listen 443 ssl http2; server_name docs.id.lendry.ru; ssl_certificate /etc/letsencrypt/live/docs.id.lendry.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/docs.id.lendry.ru/privkey.pem; location / { proxy_pass http://127.0.0.1:3003; proxy_set_header Host $host; } }` }, { type: 'code', language: 'bash', code: `sudo certbot --nginx -d id.lendry.ru -d docs.id.lendry.ru sudo nginx -t && sudo systemctl reload nginx` } ] }, { id: 'env-services', title: '6. Переменные окружения сервисов', blocks: [ { type: 'table', headers: ['Сервис', 'Ключевые переменные'], rows: [ ['sso-core', 'DATABASE_URL, JWT_*_SECRET, DATA_ENCRYPTION_KEY, REDIS_URL, RABBITMQ_URL, MINIO_*, LDAP_AUTH_URL, PUBLIC_API_URL'], ['api-gateway', 'SSO_CORE_GRPC_URL, JWT_ACCESS_SECRET, PUBLIC_API_URL, MINIO_*'], ['frontend', 'NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WS_URL'], ['docs', 'NEXT_PUBLIC_API_URL'], ['media-ws', 'REDIS_ADDR, RABBITMQ_URL, JWT_ACCESS_SECRET'], ['ldap-auth', 'PORT=8086'] ] } ] }, { id: 'migrations', title: '7. Миграции и seed', blocks: [ { type: 'code', language: 'bash', code: `# Prisma migrate внутри контейнера sso-core docker compose exec sso-core npx prisma migrate deploy # Seed системных настроек (PROJECT_NAME, LDAP_*, PIN_*) docker compose restart sso-core` }, { type: 'callout', variant: 'tip', title: 'SystemSetting', text: 'Название проекта (PROJECT_NAME), tagline и LDAP-параметры настраиваются в админке: /admin/settings. Frontend и docs подтягивают PROJECT_NAME через GET /settings/public.' } ] }, { id: 'backup', title: '8. Резервное копирование', blocks: [ { type: 'list', items: [ 'PostgreSQL: pg_dump lendry_id ежедневно', 'MinIO: mc mirror или snapshot volume minio_data', 'Redis: RDB/AOF snapshots при необходимости сохранения сессий', 'Секреты .env — хранить в vault, не в репозитории' ] }, { type: 'code', language: 'bash', code: `docker compose exec postgres pg_dump -U lendry lendry_id > backup-$(date +%F).sql` } ] }, { id: 'monitoring', title: '9. Мониторинг и обновления', blocks: [ { type: 'list', items: [ 'Health: GET /health на api-gateway и sso-core', 'Логи: docker compose logs -f --tail=200', 'Обновление: git pull && docker compose up -d --build', 'Zero-downtime: blue-green или rolling update через orchestrator (K8s / Swarm)' ] } ] } ] }, { slug: 'authentication', title: 'Аутентификация', description: 'Способы входа: пароль, OTP, LDAP, PIN и refresh-сессии.', sections: [ { id: 'flows', title: 'Сценарии входа', blocks: [ { type: 'table', headers: ['Сценарий', 'Endpoint', 'Описание'], rows: [ ['Identifier-first', 'POST /auth/identify', 'Проверка существования пользователя и способа входа'], ['Passwordless OTP', 'POST /auth/otp/send + verify', '6-значный код на почту/телефон'], ['Пароль', 'POST /auth/login/password', 'Логин + пароль или tempAuthToken после OTP'], ['LDAP/LDAPS', 'POST /auth/ldap/login', 'Корпоративный вход (требует LDAP_ENABLED)'], ['PIN', 'POST /auth/pin/verify', 'Разблокировка сессии после входа с PIN'] ] } ] }, { id: 'login-example', title: 'Примеры входа по паролю', blocks: [ { type: 'paragraph', text: 'Endpoint POST /auth/login/password принимает login (почта, телефон или username), password и метаданные устройства. Ниже — готовые примеры на разных языках.' }, { type: 'auth-login-examples' }, { type: 'callout', variant: 'info', title: 'PIN-блокировка', text: 'Если у пользователя включён PIN, ответ содержит requiresPin=true и ограниченную сессию. Полный JWT выдаётся после POST /auth/pin/verify.' } ] } ] }, { slug: 'oauth', title: 'OAuth 2.0', description: 'Регистрация приложения, Authorization Code Flow, scopes и примеры на разных языках.', sections: [ { id: 'register-app', title: 'Регистрация OAuth-приложения', blocks: [ { type: 'list', items: [ 'Войдите как супер-администратор в админ-панель', 'Перейдите в RBAC → OAuth приложения', 'Создайте клиент: name, redirectUris, scopes (openid, profile, email)', 'Confidential-клиент получит client_secret один раз — сохраните его' ] } ] }, { id: 'flow', title: 'Authorization Code Flow', blocks: [ { type: 'paragraph', text: '1) Пользователь авторизуется в Lendry ID. 2) Ваше приложение перенаправляет на GET /oauth/authorize с clientId, redirectUri, scope, userId. 3) IdP возвращает redirectUrl с authorization code. 4) Backend обменивает code на токены через POST /oauth/token. 5) GET /oauth/userinfo возвращает профиль.' }, { type: 'callout', variant: 'warning', title: 'Безопасность', text: 'client_secret никогда не храните во frontend. Обмен code → token выполняйте только на сервере.' } ] }, { id: 'examples', title: 'Примеры интеграции', blocks: [{ type: 'oauth-examples' }] }, { id: 'scopes', title: 'Scopes', blocks: [ { type: 'table', headers: ['Scope', 'Доступ'], rows: [ ['openid', 'Идентификатор пользователя (sub)'], ['profile', 'displayName, avatar, bio'], ['email', 'Основная и резервная почта'], ['phone', 'Телефоны пользователя'] ] } ] } ] }, { slug: 'ldap', title: 'LDAP / LDAPS', description: 'Корпоративный вход через Active Directory.', sections: [ { id: 'setup', title: 'Настройка в админке', blocks: [ { type: 'table', headers: ['Ключ SystemSetting', 'Описание'], rows: [ ['LDAP_ENABLED', 'Включить кнопку «Войти через LDAP»'], ['LDAP_USE_LDAPS', 'LDAPS на порту 636'], ['LDAP_HOST', 'DC-1.domain.local,DC-2.domain.local'], ['LDAP_BIND_USERNAME', 'Логин сервисной УЗ (mvkadmin)'], ['LDAP_BASE_DN', 'OU=Users,DC=domain,DC=local'], ['LDAP_BIND_PASSWORD', 'Пароль сервисной УЗ'] ] } ] }, { id: 'login', title: 'Примеры LDAP-входа', blocks: [ { type: 'paragraph', text: 'Endpoint POST /auth/ldap/login доступен при включённой настройке LDAP_ENABLED.' }, { type: 'auth-ldap-examples' } ] } ] }, { slug: 'sessions', title: 'Сессии и PIN', description: 'Управление устройствами, PIN-блокировка и отзыв сессий.', sections: [ { id: 'pin', title: 'PIN-код', blocks: [ { type: 'paragraph', text: 'PIN хранится как bcrypt hash. Таймаут блокировки читается из SystemSetting PIN_LOCK_TIMEOUT_MINUTES — значение не захардкожено во frontend.' } ] }, { id: 'devices', title: 'Устройства и сессии', blocks: [ { type: 'list', items: [ 'GET /security/users/{userId}/devices — список устройств', 'POST /security/users/{userId}/sessions/{sessionId}/revoke — выход с устройства', 'POST /security/users/{userId}/revoke-all-sessions — выход везде' ] } ] } ] }, { slug: 'family-chat', title: 'Семья и чат', description: 'Семейные группы, приглашения, чат и realtime через WebSocket.', sections: [ { id: 'family', title: 'Семейные группы', blocks: [ { type: 'paragraph', text: 'Пользователь создаёт семью, приглашает участников по email/телефону/логину. Лимиты (max family members) берутся из SystemSetting.' } ] }, { id: 'chat', title: 'Чат и WebSocket', blocks: [ { type: 'list', items: [ 'REST: /chat/groups/{groupId}/rooms, /chat/rooms/{roomId}/messages', 'WebSocket: ws://localhost:8085/ws с JWT в query или заголовке', 'Медиа чата: presigned upload + защищённый stream с Authorization' ] } ] } ] }, { slug: 'api-reference', title: 'Справочник API', description: 'Полный список REST endpoints Lendry ID API. Swagger: /api на api-gateway.', sections: [ { id: 'endpoints', title: 'Endpoints', blocks: [{ type: 'api-reference' }] }, { id: 'swagger', title: 'OpenAPI / Swagger', blocks: [ { type: 'paragraph', text: 'Интерактивная документация доступна по адресу http://localhost:3000/api (Swagger UI). Все DTO и схемы генерируются автоматически из NestJS decorators.' } ] } ] } ]; export function getDocPage(slug: string): DocPage | undefined { return docPages.find((page) => page.slug === slug); } export function getAllDocSlugs() { return docPages.map((page) => page.slug); }