Files
docs.a2v.space/public/guides/leadera-audit/index.html
T
Агальцов Антон c2cc439fc5 daily update 2026-03-31
2026-04-01 00:00:56 +03:00

816 lines
31 KiB
HTML
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.
<!DOCTYPE html>
<html lang="ru-ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Аудит Leadera — полный отчёт — docs.a2v.space</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/custom.css">
</head>
<body>
<header class="site-header">
<nav class="navbar navbar-expand-lg">
<div class="container-fluid">
<a class="navbar-brand" href="../../">docs.a2v.space</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarMain" aria-controls="navbarMain" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarMain">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="../../diary/">Дневник</a>
</li>
<li class="nav-item">
<a class="nav-link" href="../../kb/">База знаний</a>
</li>
<li class="nav-item">
<a class="nav-link" href="../../plans/">Планы</a>
</li>
<li class="nav-item">
<a class="nav-link" href="../../guides/">Инструкции</a>
</li>
<li class="nav-item">
<a class="nav-link" href="../../creds/">Креды</a>
</li>
<li class="nav-item">
<a class="nav-link" href="../../personal-site/">Личный сайт</a>
</li>
</ul>
<form class="d-flex search-form" role="search">
<input class="form-control me-2" type="search" placeholder="Поиск..." aria-label="Search" id="search-input">
<button class="btn btn-outline-light" type="submit">Найти</button>
</form>
</div>
<button class="btn btn-outline-light d-md-none ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#sidebar" aria-expanded="false" aria-controls="sidebar">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"/>
</svg>
</button>
</div>
</nav>
<div id="search-results" class="search-results" style="display: none;">
<div class="search-results-content">
<button type="button" class="btn-close float-end" id="close-search"></button>
<h5>Результаты поиска</h5>
<ul id="results-list"></ul>
</div>
</div>
</header>
<div class="container-fluid">
<div class="row">
<aside class="col-md-3 col-lg-2 d-md-block sidebar collapse" id="sidebar">
<div class="sidebar-content">
<h6>Фильтры</h6>
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="../../guides/">Все инструкции</a></li>
</ul>
<h6 class="mt-3">Теги</h6>
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="../../tags/audit/">audit</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/leadera/">leadera</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/security/">security</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/architecture/">architecture</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/ssh/">ssh</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/tunnel/">tunnel</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/vps/">vps</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/reverse-proxy/">reverse-proxy</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/nginx/">nginx</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/devops/">devops</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/openclaw/">openclaw</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/opencode/">opencode</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/angular/">angular</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/go/">go</a></li>
<li class="nav-item"><a class="nav-link" href="../../tags/docker/">docker</a></li>
</ul>
</div>
</aside>
<main class="col-md-9 col-lg-10 main-content">
<div class="article-content">
<h1>Аудит Leadera — полный отчёт</h1>
<div class="tags mb-3">
<a href="../../tags/audit/" class="tag">audit</a>
<a href="../../tags/leadera/" class="tag">leadera</a>
<a href="../../tags/security/" class="tag">security</a>
<a href="../../tags/architecture/" class="tag">architecture</a>
</div>
<h1 id="аудит-проекта-leadera">Аудит проекта Leadera</h1>
<p><strong>Дата:</strong> 30.03.2026<br>
<strong>Версия:</strong> pre-production, миграции 15</p>
<hr>
<h2 id="1-обзор-проекта">1. Обзор проекта</h2>
<p>Leadera — SaaS-система управления IT-ландшафтом с диаграммами Ганта.</p>
<h3 id="стек">Стек</h3>
<table>
<thead>
<tr>
<th>Компонент</th>
<th>Технологии</th>
</tr>
</thead>
<tbody>
<tr>
<td>Backend</td>
<td>Go 1.21+, Gin, PostgreSQL 16, pgx/v5 + sqlx, JWT (HMAC-SHA256)</td>
</tr>
<tr>
<td>Frontend</td>
<td>Angular 21.1, Bootstrap 5, NgBootstrap, RxJS, Signals</td>
</tr>
<tr>
<td>Инфраструктура</td>
<td>Docker (Colima), pm2, nginx reverse proxy, VPS reg.ru</td>
</tr>
</tbody>
</table>
<h3 id="доменная-модель">Доменная модель</h3>
<pre tabindex="0"><code>User ──┬── Space (через space_members с role)
└── GanttChart (принадлежит Space)
├── GanttSection
└── GanttTask
</code></pre><p><strong>Роли:</strong> admin, editor, viewer — назначаются <strong>на уровне Space</strong> через таблицу <code>space_members</code>.</p>
<h3 id="что-реализовано-endpoints">Что реализовано (endpoints)</h3>
<p><strong>Auth (5):</strong> register, login, refresh, change-password, logout<br>
<strong>Spaces (9):</strong> CRUD + members CRUD<br>
<strong>Gantt Charts (9):</strong> CRUD + archive + members (добавлен, но удалён в миграции 5)<br>
<strong>Gantt Sections (5):</strong> CRUD + reorder<br>
<strong>Gantt Tasks (4):</strong> CRUD</p>
<p><strong>Итого: ~32 endpoint</strong></p>
<hr>
<h2 id="2-архитектура-и-качество-кода">2. Архитектура и качество кода</h2>
<h3 id="21-backend--структура">2.1. Backend — структура</h3>
<pre tabindex="0"><code>cmd/server/main.go — точка входа
internal/
domain/ — модели (User, Space, GanttChart, etc.)
dto/ — request/response DTO (auth, space, gantt)
handlers/ — HTTP handlers (auth, space, gantt)
middleware/ — auth, cors, space, gantt, role, logging, recovery
repository/ — слой доступа к данным
service/ — бизнес-логика
config/ — конфигурация (.env)
utils/ — jwt, password, response, validation
migrations/ — SQL миграции (1-5)
</code></pre><p><strong>Оценка:</strong> ✅ Чистая layered архитектура. Разделение ответственности чёткое.</p>
<h3 id="22-frontend--структура">2.2. Frontend — структура</h3>
<pre tabindex="0"><code>src/app/
core/auth/ — auth service, interceptor
core/models/ — app.model, space.model
core/services/ — space.service, space-storage.service
pages/auth/ — sign-in, sign-up, reset-pw, etc.
pages/gantt/ — компоненты, модалки, сервисы, модели
pages/dashboards/ — spaces list, dashboard
layouts/ — navbar, topbar, main-layout
</code></pre><p><strong>Оценка:</strong> ✅ Стандартная Angular-структура. Signals для state management.</p>
<h3 id="23-проблемы-кода">2.3. Проблемы кода</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Проблема</th>
<th>Критичность</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td>C1</td>
<td><code>\r</code> в service-файлах</td>
<td>Medium</td>
<td>gantt_service.go, gantt_section_service.go содержат CRLF</td>
</tr>
<tr>
<td>C2</td>
<td><code>main.go:238</code> — мёртвый код <code>getUserHandler</code></td>
<td>Low</td>
<td>Неиспользуемая функция</td>
</tr>
<tr>
<td>C3</td>
<td>Swagger не сгенерирован</td>
<td>Medium</td>
<td>Аннотации есть, но <code>api/</code> директории нет</td>
</tr>
<tr>
<td>C4</td>
<td><code>RegisterProtectedRoutes</code> не вызывается в main.go</td>
<td><strong>Critical</strong></td>
<td>GET/PUT/DELETE <code>/spaces/:id</code> не зарегистрированы</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="3-права-доступа--главный-вопрос">3. Права доступа — главный вопрос</h2>
<h3 id="31-текущая-реализация">3.1. Текущая реализация</h3>
<p>Права назначаются <strong>исключительно через Space</strong>:</p>
<ol>
<li>Таблица <code>space_members</code> — связка user ↔ space с role (admin/editor/viewer)</li>
<li><code>SpaceMiddleware.RequireSpace()</code> — проверяет членство и записывает <code>ContextSpaceRole</code> в context</li>
<li><code>RoleMiddleware.RequireMinRole()</code> — проверяет иерархию ролей (admin &gt; editor &gt; viewer)</li>
</ol>
<p><strong>Диаграммы Ганта НЕ имеют собственной системы ролей.</strong> Таблица <code>gantt_chart_members</code> была создана (миграция 4) и тут же удалена (миграция 5). Все проверки доступа к диаграммам идут через space membership.</p>
<h3 id="32-как-это-работает">3.2. Как это работает</h3>
<pre tabindex="0"><code>Запрос → AuthMiddleware (JWT → userID)
→ SpaceMiddleware.RequireSpace() (проверяет space_members → ContextSpaceRole)
→ GanttMiddleware.RequireGanttChart() (проверяет chart.space_id == spaceID)
→ RoleMiddleware.RequireMinRole(&#34;editor&#34;) (проверяет ContextSpaceRole)
</code></pre><p><strong>Это правильно.</strong> Любой член Space с ролью editor+ может создавать/редактировать диаграммы. Viewers — только читать.</p>
<h3 id="33--критическая-проблема-frontend-не-соответствует-backend">3.3. 🔴 Критическая проблема: Frontend не соответствует Backend</h3>
<p><strong>Frontend <code>MembersModalComponent</code></strong> пытается управлять участниками <strong>диаграммы</strong>:</p>
<ul>
<li><code>ganttService.addMember(chartId, dto)</code><code>/api/v1/spaces/{spaceId}/gantt-charts/{chartId}/members</code></li>
<li><code>ganttService.updateMemberRole(chartId, userId, dto)</code><code>.../members/{userId}</code></li>
<li><code>ganttService.removeMember(chartId, userId)</code><code>.../members/{userId}</code></li>
</ul>
<p><strong>Backend <code>GanttHandler.RegisterRoutes()</code> НЕ регистрирует маршруты для members диаграмм.</strong> После удаления <code>gantt_chart_members</code> (миграция 5) эти endpoints не существуют.</p>
<p><strong>Результат:</strong> модальное окно «Участники диаграммы» на фронтенде <strong>не работает</strong> — все вызовы вернут 404.</p>
<h3 id="34--проблема-role-fallback">3.4. 🔴 Проблема: Role fallback</h3>
<p>В <code>role_middleware.go:28-31</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">role</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">GetString</span>(<span style="color:#a6e22e">ContextSpaceRole</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">role</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">role</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">GetString</span>(<span style="color:#a6e22e">ContextRole</span>) <span style="color:#75715e">// fallback на глобальную роль User</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Если <code>ContextSpaceRole</code> не установлен, используется <strong>глобальная роль пользователя</strong> (из JWT). Это значит:</p>
<ul>
<li>Пользователь с <code>role=admin</code> в таблице <code>users</code> получит admin-доступ к <strong>любому</strong> space, даже если он не член этого space</li>
<li>Но <code>SpaceMiddleware</code> проверяет членство раньше, так что в цепочке <code>RequireSpace() → RequireMinRole()</code> это не эксплуатируется</li>
<li>Однако если <code>RequireMinRole()</code> используется <strong>без</strong> <code>RequireSpace()</code> — это дыра</li>
</ul>
<p><strong>Рекомендация:</strong> Удалить fallback на <code>ContextRole</code>. Если <code>ContextSpaceRole</code> пуст — отказывать.</p>
<h3 id="35-рекомендации-по-правам-доступа">3.5. Рекомендации по правам доступа</h3>
<ol>
<li><strong>Удалить модалку участников диаграммы</strong> на фронтенде — она мертва</li>
<li><strong>Или</strong> добавить раздел «Участники Space» с возможностью управления ролями через <code>/spaces/:space_id/members</code> (эти endpoints работают)</li>
<li>Удалить fallback на <code>ContextRole</code> в <code>role_middleware.go</code></li>
<li>Добавить проверку: editor не может повысить кого-то до admin (сейчас может — <code>AddMember</code> разрешает editor+ назначать любую роль)</li>
</ol>
<hr>
<h2 id="4-безопасность">4. Безопасность</h2>
<h3 id="41-критические">4.1. Критические</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Проблема</th>
<th>Файл</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td>S1</td>
<td>JWT Secret = test</td>
<td><code>.env</code></td>
<td><code>JWT_SECRET=test_secret_key_change_in_production</code><strong>это прод-конфиг!</strong></td>
</tr>
<tr>
<td>S2</td>
<td>CORS: <code>*</code></td>
<td><code>.env</code></td>
<td><code>CORS_ALLOWED_ORIGINS=*</code> — любой домен может обращаться к API</td>
</tr>
<tr>
<td>S3</td>
<td>Пароль БД в .env</td>
<td><code>.env</code></td>
<td><code>DB_PASSWORD=popoloka16</code> — creds в открытом виде</td>
</tr>
<tr>
<td>S4</td>
<td>Refresh tokens: SHA256</td>
<td><code>utils/password.go</code></td>
<td>SHA256 вместо bcrypt для refresh token hash — быстро брутфорсится</td>
</tr>
</tbody>
</table>
<h3 id="42-высокие">4.2. Высокие</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Проблема</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td>S5</td>
<td>Нет rate limiting</td>
<td>Brute-force на <code>/auth/login</code> без ограничений</td>
</tr>
<tr>
<td>S6</td>
<td>Нет CSRF protection</td>
<td>API принимает POST/PUT/DELETE без CSRF-токена</td>
</tr>
<tr>
<td>S7</td>
<td>Нет CSP заголовков</td>
<td>X-Frame-Options, Content-Security-Policy не настроены</td>
</tr>
<tr>
<td>S8</td>
<td>JWT в localStorage</td>
<td>XSS может украсть access + refresh tokens</td>
</tr>
</tbody>
</table>
<h3 id="43-средние">4.3. Средние</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Проблема</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td>S9</td>
<td>Нет password policy</td>
<td>Минимальные требования к паролю не enforced</td>
</tr>
<tr>
<td>S10</td>
<td>Нет audit log</td>
<td>Действия с spaces/charts не логируются для аудита</td>
</tr>
<tr>
<td>S11</td>
<td>Нет блокировки аккаунта</td>
<td>Неограниченные попытки входа</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="5-инфраструктура">5. Инфраструктура</h2>
<h3 id="51-текущее-состояние">5.1. Текущее состояние</h3>
<table>
<thead>
<tr>
<th>Компонент</th>
<th>Статус</th>
<th>Примечание</th>
</tr>
</thead>
<tbody>
<tr>
<td>Go API (pm2: leadera-api)</td>
<td>⚠️</td>
<td>Порт 8080, хост 0.0.0.0</td>
</tr>
<tr>
<td>Angular FE (pm2: leadera-fe)</td>
<td>⚠️</td>
<td>Порт 4200, хост 0.0.0.0</td>
</tr>
<tr>
<td>PostgreSQL (Docker)</td>
<td></td>
<td>Порт 5432, контейнер app-leadera-db</td>
</tr>
<tr>
<td>VPS reverse proxy</td>
<td>⚠️</td>
<td>sand.a2v.space → 8082/8083, но 502 если сервисы не запущены</td>
</tr>
<tr>
<td>SSL</td>
<td></td>
<td>Let&rsquo;s Encrypt на VPS</td>
</tr>
</tbody>
</table>
<h3 id="52-проблемы">5.2. Проблемы</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Проблема</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td>I1</td>
<td>Docker для БД, нативно для API</td>
<td>Несогласованность — API и FE не в Docker</td>
</tr>
<tr>
<td>I2</td>
<td>Нет health-check в pm2</td>
<td>API может упасть без перезапуска</td>
</tr>
<tr>
<td>I3</td>
<td>Нет CI/CD</td>
<td>Ручной деплой через <code>make run</code> + pm2</td>
</tr>
<tr>
<td>I4</td>
<td>Нет nginx на Mac Studio</td>
<td>FE и API торчат наружу напрямую</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="6-функциональная-оценка">6. Функциональная оценка</h2>
<h3 id="61-что-работает">6.1. Что работает</h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Backend</th>
<th>Frontend</th>
<th>Статус</th>
</tr>
</thead>
<tbody>
<tr>
<td>Регистрация/логин</td>
<td></td>
<td></td>
<td>Работает</td>
</tr>
<tr>
<td>JWT auth + refresh</td>
<td></td>
<td></td>
<td>Работает</td>
</tr>
<tr>
<td>Spaces CRUD</td>
<td>⚠️</td>
<td></td>
<td><code>RegisterProtectedRoutes</code> не вызван (C4)</td>
</tr>
<tr>
<td>Space members</td>
<td></td>
<td></td>
<td>Работает</td>
</tr>
<tr>
<td>Gantt charts CRUD</td>
<td></td>
<td></td>
<td>Работает</td>
</tr>
<tr>
<td>Gantt sections</td>
<td></td>
<td></td>
<td>Работает</td>
</tr>
<tr>
<td>Gantt tasks</td>
<td></td>
<td>⚠️</td>
<td>Нет UI для задач внутри диаграммы</td>
</tr>
<tr>
<td>Архивация диаграмм</td>
<td></td>
<td></td>
<td>Работает</td>
</tr>
<tr>
<td>Members диаграммы</td>
<td></td>
<td>✅ (мёртвый UI)</td>
<td>Backend endpoints не существуют</td>
</tr>
</tbody>
</table>
<h3 id="62-что-не-работает-или-отсутствует">6.2. Что НЕ работает или отсутствует</h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Статус</th>
</tr>
</thead>
<tbody>
<tr>
<td>Визуализация Ганта (таймлайн)</td>
<td>❌ Нет компонента отрисовки</td>
</tr>
<tr>
<td>Drag &amp; Drop задач</td>
<td></td>
</tr>
<tr>
<td>Real-time collaboration</td>
<td></td>
</tr>
<tr>
<td>Уведомления</td>
<td></td>
</tr>
<tr>
<td>Поиск</td>
<td></td>
</tr>
<tr>
<td>Экспорт (PDF/Excel)</td>
<td></td>
</tr>
<tr>
<td>Email verification</td>
<td></td>
</tr>
<tr>
<td>Password reset</td>
<td>Backend есть, UI есть — не проверено</td>
</tr>
</tbody>
</table>
<h3 id="63-оценка-готовности">6.3. Оценка готовности</h3>
<p><strong>Система на стадии каркаса.</strong> Backend API функционален для CRUD операций, но:</p>
<ul>
<li>Визуализация Ганта — основная фича — не реализована (нет canvas/SVG рендеринга)</li>
<li>Управление участниками только на уровне Space</li>
<li>Нет CI/CD, тестов, мониторинга</li>
</ul>
<hr>
<h2 id="7-приоритизированный-план-исправлений">7. Приоритизированный план исправлений</h2>
<h3 id="неделя-1--критическое-безопасность">Неделя 1 — Критическое (безопасность)</h3>
<table>
<thead>
<tr>
<th>Приоритет</th>
<th>Задача</th>
<th>Трудоёмкость</th>
</tr>
</thead>
<tbody>
<tr>
<td>P0</td>
<td>Заменить JWT secret на криптостойкий</td>
<td>1 час</td>
</tr>
<tr>
<td>P0</td>
<td>Настроить CORS (только sand.a2v.space)</td>
<td>1 час</td>
</tr>
<tr>
<td>P0</td>
<td>bcrypt для refresh tokens вместо SHA256</td>
<td>2 часа</td>
</tr>
<tr>
<td>P0</td>
<td>Удалить fallback на ContextRole в role_middleware</td>
<td>30 мин</td>
</tr>
<tr>
<td>P0</td>
<td>Исправить <code>RegisterProtectedRoutes</code> — зарегистрировать маршруты</td>
<td>1 час</td>
</tr>
<tr>
<td>P1</td>
<td>Rate limiting на auth endpoints</td>
<td>4 часа</td>
</tr>
<tr>
<td>P1</td>
<td>Editor не может назначать admin роль</td>
<td>2 часа</td>
</tr>
</tbody>
</table>
<h3 id="неделя-2--архитектура-прав">Неделя 2 — Архитектура прав</h3>
<table>
<thead>
<tr>
<th>Приоритет</th>
<th>Задача</th>
<th>Трудоёмкость</th>
</tr>
</thead>
<tbody>
<tr>
<td>P1</td>
<td>Удалить/переписать MembersModalComponent → Space members</td>
<td>4 часа</td>
</tr>
<tr>
<td>P1</td>
<td>Добавить страницу управления участниками Space</td>
<td>8 часов</td>
</tr>
<tr>
<td>P2</td>
<td>HttpOnly cookies для JWT (вместо localStorage)</td>
<td>8 часов</td>
</tr>
<tr>
<td>P2</td>
<td>CSRF protection</td>
<td>4 часа</td>
</tr>
</tbody>
</table>
<h3 id="неделя-3--функциональность">Неделя 3 — Функциональность</h3>
<table>
<thead>
<tr>
<th>Приоритет</th>
<th>Задача</th>
<th>Трудоёмкость</th>
</tr>
</thead>
<tbody>
<tr>
<td>P2</td>
<td>Визуализация Ганта (canvas/d3)</td>
<td>40+ часов</td>
</tr>
<tr>
<td>P2</td>
<td>Drag &amp; Drop для задач</td>
<td>16 часов</td>
</tr>
<tr>
<td>P3</td>
<td>Тесты (unit + integration)</td>
<td>24 часа</td>
</tr>
<tr>
<td>P3</td>
<td>CI/CD pipeline</td>
<td>8 часов</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="8-резюме">8. Резюме</h2>
<h3 id="ключевые-выводы">Ключевые выводы</h3>
<ol>
<li><strong>Права доступа</strong> — архитектурно верно: роль назначается через Space, не через диаграмму. Но фронтенд ещё не обновлён после удаления <code>gantt_chart_members</code>.</li>
<li><strong>Безопасность</strong> — критические уязвимости в конфигурации (JWT secret, CORS, bcrypt). Требуют немедленного исправления до любого публичного деплоя.</li>
<li><strong>Функциональность</strong> — CRUD работает, но основная ценность (визуализация Ганта) не реализована.</li>
<li><strong>Код</strong> — чистый, хорошая архитектура, но есть мёртвый код и незарегистрированные маршруты.</li>
</ol>
<h3 id="общая-оценка--pre-alpha">Общая оценка: 🔴 Pre-alpha</h3>
<p>Проект готов к внутренней демонстрации CRUD, но не готов к продакшену. Первоочередное — безопасность и исправление мёртвых endpoints.</p>
</div>
</main>
</div>
</div>
<footer class="site-footer">
<div class="container">
<p class="text-center mb-0">&copy; 2026 docs.a2v.space</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js@7.0.0/dist/fuse.min.js"></script>
<script src="../../js/search.js"></script>
<script src="../../js/creds.js"></script>
</body>
</html>