Files
IdP/apps/docs/lib/docs-pages.ts
2026-06-24 14:37:15 +03:00

616 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 <repository-url> 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);
}