Архітектурний аудит проєкту ESWF¶
Дата: 2026-03-26 Роль: Lead System Architect + Risk Management
Масштаб проєкту¶
| Метрика | Значення |
|---|---|
| Сирцевий код | ~146,000 рядків у ~995 файлах |
| Фронтенди | 4 (Portal, News, DOP, Shop) |
| Мобільні додатки | 2 (Driver, Sales) |
| Бекенд Django apps | 15 модулів, 95+ моделей |
| Міграції БД | 153+ |
| Тести | 0 |
1. КРИТИЧНІ РИЗИКИ (зупинять проєкт)¶
1.1. Абсолютний нуль тестового покриття¶
- 0 тестів на бекенді (14 apps, усі tests.py — порожні заглушки)
- 0 тестів на фронтенді (65,000 рядків DOP без жодного .test.tsx)
- 0 тестів у мобільних додатках
- Навіть тестові бібліотеки (Vitest, Jest, pytest) не встановлені
Наслідки: - Будь-який рефакторинг — рулетка - Неможливо безпечно оновити залежності - Неможливо онбордити нового розробника - Регресії накопичуються непомітно
1.2. Відсутність Error Boundaries у фронтенді¶
Жодного ErrorBoundary компоненту в усьому DOP. Якщо будь-який компонент кине помилку під час рендеру — весь додаток падає на білий екран.
1.3. Монолітні "god-компоненти"¶
| Файл | Рядків | Проблема |
|---|---|---|
| AdminToolsPage.tsx | 2,010 | UI + логіка + API + стейт в одному файлі |
| ChatPage.tsx | 1,464 | WebSocket + рендеринг + бізнес-логіка |
| ChatDrawer.tsx | 1,345 | Той самий антипатерн |
| SalesFieldManager.tsx | 1,265 | Моноліт |
| ETTNPage.tsx | 1,077 | Ed25519 + UI + форми |
2. ВИСОКІ РИЗИКИ (нагромаджуються)¶
2.1. Нуль моніторингу та observability¶
- Немає Sentry / Datadog / New Relic
- Немає структурованого логування
- Немає health-check ендпоінтів
- Немає метрик (Prometheus, StatsD)
- Немає APM
2.2. Відсутність CI/CD пайплайну¶
- Немає GitHub Actions / GitLab CI
- Немає Dockerfile / docker-compose
- Немає автоматизованих перевірок при PR
2.3. Якість git-історії¶
Останні 20 комітів — переважно "fix" без контексту. Неможливо зрозуміти що було змінено і навіщо.
2.4. Безпека UniversalViewSet¶
- Sort field інтерполюється напряму в order_by() без whitelist
- Filter parameters приймають довільні імена полів
- Немає ліміту pageSize — запит ?pageSize=999999 може вбити сервер
- Google OAuth створює юзерів для будь-якого Google-акаунту без whitelist доменів
2.5. Немає code splitting у фронтенді¶
Усі роути імпортуються на top-level. Немає React.lazy(). Весь DOP завантажується одним бандлом.
3. СЕРЕДНІ РИЗИКИ (технічний борг)¶
3.1. 153+ міграцій без squash¶
essentials і fleet мають по 43-44 міграції кожний.
3.2. Accessibility (a11y) — практично відсутня¶
З 188 TSX-файлів лише 2 мають aria-label. Не пройде WCAG 2.1 AA.
3.3. API-клієнт без timeout і retry¶
JWT refresh реалізований коректно, але немає request timeout і retry стратегії.
3.4. Одна версія API без deprecation strategy¶
Усе на /api/v1/ — немає плану версіонування, немає CHANGELOG.
3.5. Зберігання API-ключів ContainerHub¶
api_key зберігається як звичайний CharField. Повинні шифруватися at rest.
3.6. Монорепо без інструментів монорепо¶
6 фронтендів + 2 мобільних + бекенд без Nx / Turborepo. Порожня frontend/shared/.
4. ЩО ДОБРЕ¶
| Аспект | Оцінка |
|---|---|
| TypeScript strict mode | Увімкнений, агресивний лінтинг |
| Секрети | .env в .gitignore, env vars у settings |
| WebSocket auth | JWT + tenant isolation + membership check |
| Multi-tenancy | Коректна ізоляція через middleware + abstract models |
| PWA | Workbox + offline API caching + auto-update |
| Zustand stores | Чисто розділені, правильна персистенція |
| i18n | en/ua across усіх фронтендів |
| Django production settings | SSL, HSTS, secure cookies |
| Metadata-driven UI | EntityRegistry + UniversalViewSet |
5. ПЛАН УСУНЕННЯ¶
Фаза 1 — Стабілізація (тижні 1-4)¶
- [x] Vitest + React Testing Library для DOP — 80 тестів (utils, stores, API, LoginPage, ErrorBoundary)
- [ ] pytest для бекенду — покрити UniversalViewSet, permissions, serializers
- [x] Error Boundaries — глобальний (App.tsx) + per-section (SectionPage.tsx) + 8 тестів
- [x] Sentry — інтеграція (frontend + backend)
- [x] Ліміт pageSize (1-500) і whitelist sort/filter полів у UniversalViewSet
Фаза 2 — Інфраструктура (тижні 5-8)¶
- [ ] GitHub Actions — lint + typecheck + test на кожен PR
- [ ] .env.example + setup скрипт — документований onboarding
- [x] Code splitting — React.lazy() для маршрутів, vendor chunks (index.js: 6MB→1.6MB, -74%)
- [ ] Request timeout + retry в API клієнті
- [ ] Conventional commits — husky + commitlint
Фаза 3 — Масштабування (тижні 9-12)¶
- [ ] Рефакторинг god-компонентів (1000+ рядків)
- [ ] Squash міграцій
- [ ] API versioning strategy
- [ ] Accessibility audit
- [ ] Шифрування API-ключів at rest
6. SENTRY — ІНСТРУКЦІЯ З ПІДКЛЮЧЕННЯ¶
Як отримати DSN¶
- Зареєструватися на sentry.io (безкоштовний план: 5K помилок/міс)
- Створити організацію → створити 2 проєкти:
- ESWF-Frontend (платформа: React)
- ESWF-Backend (платформа: Django)
- У кожному проєкті: Settings → Client Keys (DSN) → скопіювати DSN
DSN має формат: https://<public_key>@o<org_id>.ingest.sentry.io/<project_id>
Змінні оточення¶
# Frontend (.env або .env.production)
VITE_SENTRY_DSN=https://xxx@o123.ingest.sentry.io/456
# Backend (.env)
SENTRY_DSN=https://yyy@o123.ingest.sentry.io/789
SENTRY_ENVIRONMENT=production # або staging, development
Що вже інтегровано¶
Frontend (DOP):
- src/lib/sentry.ts — initSentry(), captureError(), setSentryUser(), clearSentryUser()
- main.tsx — initSentry() викликається перед рендером
- App.tsx — глобальний ErrorBoundary передає помилки в Sentry
- SectionPage.tsx — per-section ErrorBoundary передає помилки в Sentry
- authStore.ts — login/logout встановлює/очищує користувача в Sentry
- Автоматично: BrowserTracing (performance), Session Replay (10% помилок)
- У dev-режимі помилки не відправляються (beforeSend фільтрує localhost)
Backend (Django):
- settings/base.py — sentry_sdk.init() при наявності SENTRY_DSN
- traces_sample_rate=0.2 (20% запитів для performance)
- profiles_sample_rate=0.1 (10% профілювання)
- send_default_pii=True (прив'язка помилок до користувачів)
Без DSN¶
Якщо SENTRY_DSN / VITE_SENTRY_DSN не задані — Sentry не ініціалізується, overhead нульовий. Додаток працює як раніше.
7. ІНСТРУКЦІЯ — ЯК ЗАПУСКАТИ ТЕСТИ І ПЕРЕВІРЯТИ КОРЕКТНІСТЬ¶
Швидкий старт¶
Запуск тестів¶
| Команда | Що робить |
|---|---|
npx vitest run |
Запустити всі тести один раз і вийти |
npx vitest |
Watch-режим — тести перезапускаються при зміні файлів |
npx vitest run src/store/ |
Запустити тести лише з папки src/store/ |
npx vitest run src/utils/vatUtils.test.ts |
Запустити конкретний файл тестів |
npx vitest run --reporter=verbose |
Детальний вивід (кожен тест окремо) |
Покриття коду (coverage)¶
Згенерує звіт у терміналі: скільки % рядків, функцій та гілок покрито тестами.
Що зараз покрито тестами (80 тестів)¶
| Файл | Кількість | Що тестує |
|---|---|---|
src/utils/vatUtils.test.ts |
17 | ПДВ розрахунки (calcVatLine, calcDocumentTotals, effectiveRate) |
src/api/entities.test.ts |
7 | toApiPath — конвертація camelCase → kebab-case |
src/api/client.test.ts |
4 | JWT interceptor, refresh token, logout на 401 |
src/store/authStore.test.ts |
7 | Login, logout, tokens, bootstrapped state |
src/store/tabsStore.test.ts |
10 | Відкриття/закриття вкладок, activeTab |
src/store/uiStore.test.ts |
12 | Тема, мова, sidebar, preferences sync |
src/store/settingsStore.test.ts |
8 | Бухгалтерські налаштування, чат |
src/pages/LoginPage.test.tsx |
6 | Рендер форми, помилки, навігація |
src/components/shared/ErrorBoundary.test.tsx |
8 | Перехоплення помилок, recovery, fallback |
Перевірка збірки¶
Якщо збірка успішна — побачиш список чанків з розмірами і ✓ built in X s.
Перевірка бекенду¶
cd c:\eswf\backend
# Перевірити що Django стартує без помилок
python manage.py check
# Перевірити міграції
python manage.py migrate --check
Коли запускати тести¶
- Після зміни бізнес-логіки (vatUtils, stores, API client) →
npx vitest run - Після оновлення залежностей (npm update) →
npx vitest run+npx vite build - Перед комітом — переконатись що нічого не зламалось
- При додаванні нового коду — написати тест поруч (файл
*.test.tsабо*.test.tsx)
Як додати новий тест¶
- Створи файл поруч з модулем:
myModule.test.ts(або.test.tsxдля компонентів) - Імпортуй те, що тестуєш:
- Напиши тест:
- Для React компонентів використовуй хелпер:
- Запусти:
npx vitest run src/path/myModule.test.ts
Структура тестової інфраструктури¶
src/test/
├── setup.ts # Глобальний setup (jest-dom, mocks для window.matchMedia, ResizeObserver)
└── renderWithProviders.tsx # Обгортка: MantineProvider + MemoryRouter + QueryClient + i18n
Конфігурація: vitest.config.ts у корені frontend/erp/.
App taxonomy (2026-04-28)¶
DOP has 4 application categories. Source of truth — frontend config/types.ts SectionItem.appType + backend shop.Product.app_type.
| Type | Role | Examples |
|---|---|---|
core |
Base system, cannot be uninstalled | Essentials |
module |
Vertical functional block (own backend app, own section) | Fleet, Logistic, ContainerHub |
plugin |
Functional layer on top of a module — most horizontal apps live here | Accounting, CRM, HRM & Payroll, Production, Budgeting, Consolidation, FleetTrack, E-Commerce Manager, Purchase Invoice, Quality Control, DOP Chat |
integration |
Connector to an external system | M.E.Doc, Bank Exchange, BAF Sync, eTTN |
addon is deprecated — renamed to plugin 2026-04-28 (shop migration 0009, frontend applications.ts mass-edit). The old term still appears in legacy commit messages and product docs but should not be reintroduced.
Sidebar accordion¶
Sidebar.tsx renders Mantine Accordion:
- top level — core + module items where !sidebarHidden
- inside each branch — two buckets, Plugins and Integrations, populated from forModules[0] (or sidebarParent override)
- hidden from sidebar but kept in the AppStore catalogue: appstore, appPortal (separate Next.js site), appDriverApp (native mobile)
Open branches persist in uiStore.sidebarAccordionOpen (default ['appEssentials']).
Tree builder: buildSidebarTree — sibling of buildDependencyTree, both consume the same applications.ts items. Plan: docs/planning/accordion-sidebar.md.