You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

33 KiB

Инвентаризация backend и справочный gap-analysis с tgFlaskForm

Назначение. §1–§9 — полная карта того, что сейчас живёт в backend/ (Node.js / Express). Используется в Этапе 1 как чек-лист «что переписать на Flask внутри flask_app/». §10 — справочный gap-analysis между этим и уже готовым модулем cabinet/testing в HR_TG_Bot/tgFlaskForm — пригодится в Этапе 2 при слиянии.

Связано: migration-final.md (главный трекер двух этапов), migration-to-tgflaskform.md (план Этапа 2 — слияние с tgFlaskForm), PROJECT_STATUS.md, СПРИНТЫ_МОБИЛЬНЫЙ_ДИЗАЙН.md.

Прочитайте сначала migration-final.md — там зафиксировано разделение на Этап 1 (унификация стека внутри TestingWebApp, БД остаётся clinic_tests) и Этап 2 (слияние с HR-кабинетом, БД мигрирует на hr_bot_test). Содержимое §10 этого документа — материал для Этапа 2.


1. Карта HTTP-эндпоинтов (всё, что зовёт фронтенд)

Источник: backend/src/app.js, backend/src/routes/*.js. Перепроверено по фактическим вызовам из frontend/src/** (frontend/src/api.js — общий fetch с credentials: 'include').

# Метод Путь Зовёт из фронта Auth Сервис(ы) Особенности
1 GET /api/health нет inline smoke-проверка
2 POST /api/auth/login Login.jsx нет utils/auth, db/db, db/hrPool, utils/werkzeugPassword, utils/hrRoleMap, config/authConstants bcrypt (dev) + Werkzeug (HR) ветки; UPSERT users по staff_id; кладёт JWT в HTTP-only cookie
3 POST /api/auth/logout CabinetLayout.jsx нет inline очистить cookie
4 GET /api/auth/me CabinetLayout.jsx cookie JWT middleware/auth + featureFlags возвращает пользователя + флаги UI (devUi, assignmentUi)
5 GET /api/auth/dev/assignment-directory TestDetail.jsx (assign-блок) cookie JWT, feature-flag services/assignmentDirectoryService сливает HR (staff_members, employees_departments) + clinic_tests.users; query: q, department, clinic
6 POST /api/tests/import/document TestDetail.jsx («Документ в вопросы») cookie JWT services/documentExtractService (PDF/DOCX/TXT), services/documentGenServiceservices/llmClient multer (10 МБ, OS tmpdir); удаляет файл после извлечения
7 GET /api/tests TestsList.jsx cookie JWT services/testAccessService.queryTestsVisibleToUser + inline (hiddenByYou) каталог + список «скрытые мной»
8 POST /api/tests TestsList.jsx cookie JWT services/testDraftService.createTestWithVersion создаёт цепочку + версию 1
9 GET /api/tests/:id/summary TestDetail.jsx cookie JWT inline + testAccessService.userHasTestAccess карточка цепочки (одна строка)
10 GET /api/tests/:id/versions TestDetail.jsx cookie JWT, только автор inline + testChainService.hasAnyAttemptForTest список версий + флаг hasAttempts
11 GET /api/tests/:id/editor TestDetail.jsx cookie JWT, только автор testAttemptService.getEditorContent вопросы активной версии с правильными ответами
12 POST /api/tests/:id/ai/generate-test TestDetail.jsx (ИИ — целиком) cookie JWT, только автор aiEditorService.generateFullTestByShapellmClient строгая сетка shape: [{optionsCount, hasMultipleAnswers}]; до 40 вопросов
13 POST /api/tests/:id/ai/generate-question TestDetail.jsx (ИИ — один вопрос) cookie JWT, только автор aiEditorService.generateOrRephraseQuestionllmClient пустой текст → новый вопрос; непустой → переформулировка
14 POST /api/tests/:id/versions/:vid/activate TestDetail.jsx cookie JWT, только автор inline транзакция; снимает is_active со всех версий, потом ставит на :vid
15 PATCH /api/tests/:id TestDetail.jsx cookie JWT, только автор inline chainActive (true/false) — публикация в каталоге
16 POST /api/tests/:id/assign TestDetail.jsx (назначение) cookie JWT, только автор, feature-flag assignmentUserService.ensureClinicUserIdForStaff принимает userIds[]/staffIds[]/legacy userId/staffId; одна строка test_assignments + N строк test_assignment_targets
17 POST /api/tests/:id/draft TestDetail.jsx («Сохранить») cookie JWT, только автор testDraftService.saveTestDraft если есть попытки и переданы questions — fork новой версии (V.3)
18 POST /api/tests/:id/attempts/start TestsList.jsx cookie JWT (доступ через userHasTestAccess) testAccessService.userHasTestAccess + inline новая попытка по активной версии
19 GET /api/tests/:id/attempts TestDetail.jsx («Прохождения») cookie JWT, только автор testAttemptService.listTestAttemptsForAuthor до 200 попыток по всем версиям
20 GET /api/tests/:id/attempts/:aid/review TestAttemptReview.jsx cookie JWT (владелец или автор) testAttemptService.getAttemptReviewForUserbuildReviewFromDb разбор попытки
21 GET /api/tests/:id/attempts/:aid/play TestAttempt.jsx cookie JWT (владелец) testAttemptService.getPlayContent вопросы без правильных ответов
22 POST /api/tests/:id/attempts/:aid/submit TestAttempt.jsx cookie JWT (владелец) testAttemptService.submitAttempt FOR UPDATE, проверка ответов, перезапись user_answers, статус completed
23 GET /api/tests/:id/chain-info TestDetail.jsx cookie JWT (через userHasTestAccess) testAccessService + testChainService флаг hasAnyAttempt

Итого: 22 функциональных эндпоинта (без /api/health). Все ответы — JSON. Все входы — JSON или multipart/form-data (только /import/document).


2. Сервисный уровень (что должно появиться в Flask)

Файл (Node) Что делает Что нужно в Python
services/testAccessService.js Запросы каталога; userHasTestAccess(testId, userId) SQLAlchemy/psycopg2 версия двух запросов
services/testDraftService.js createTestWithVersion, saveTestDraft, forkNewVersion, replaceVersionContent, copyQuestionTree Транзакции на psycopg2/SQLAlchemy; следить за частичным уникальным индексом uq_test_versions_one_active_per_test
services/testAttemptService.js getEditorContent, getPlayContent, submitAttempt, buildReviewFromDb, getAttemptReviewForUser, listTestAttemptsForAuthor; вычисление баллов Самый объёмный модуль; внимательно с массивами UUID (user_answers.selected_options uuid[])
services/testChainService.js hasAnyAttemptForTest Один SQL EXISTS
services/aiEditorService.js parseAndValidateShape, generateFullTestByShape, generateOrRephraseQuestion, assertDraftMatchesShape Чистый Python; зависит от llmClient и documentGenService.validate*
services/documentGenService.js parseJsonFromLlmText, validateAndNormalizeDraft, generationForImportDocument Чистый Python (json.loads + валидация формы)
services/documentExtractService.js resolveDocumentKind, extractTextFromFile, extractTextFromBuffer Замены пакетов: mammothpython-docx или mammoth.py; pdf-parsepypdf (или pdfminer.six)
services/llmClient.js OpenAI-совместимый Chat Completions, JSON-mode, таймаут 120 с httpx / requests + явный timeout
services/assignmentDirectoryService.js Слияние clinic_tests.users ↔ HR (staff_members, employees_departments); фильтры q/department/clinic Два пула; psycopg2 достаточно
services/assignmentUserService.js ensureClinicUserIdForStaff (UPSERT по staff_id) UPSERT с ON CONFLICT (staff_id)
utils/auth.js bcrypt + JWT + (Werkzeug fallback) passlib[bcrypt] или bcrypt; flask-jwt-extended или ручной pyjwt
utils/werkzeugPassword.js scrypt:$N:$r:$p$salt$hex, pbkdf2:sha256:iter$salt$hex Уже родной Python: werkzeug.security.check_password_hash
utils/hrRoleMap.js строка HR-роли → 'hr'/'manager'/'employee' Один def map_hr_role()
middleware/auth.js authenticate, requireRole, requireDepartment, optionalAuth Flask-декоратор @login_required/@roles_required (или before_request)
config/featureFlags.js isAssignmentFeatureEnabled Простая функция от env
config/devAuthor.js isTestAuthor(createdBy, userId) Один сравнительный helper
config/authConstants.js HR_MANAGED_PASSWORD_PLACEHOLDER, isHrAuthEnabled Без изменений
messages/ru.js Текстовые сообщения API app/messages/ru.py (dict)

Особое внимание: testAttemptService.submitAttempt использует FOR UPDATE и выгружает все answer_options версии разом. Простую построчную проверку «правильно/нет» делает Python-функция same_selection(set, set). На больших тестах помогает индекс idx_answer_options_question_id — он уже есть.


3. Базы данных и схемы

3.1 Основная — clinic_tests (DATABASE_URL)

Из backend/src/db/migrations/001_initial.sql:

  • departments (UUID, name)
  • users (UUID, login UNIQUE, password_hash, full_name, role user_role, department_id FK, is_active, staff_id — миграция 003, UNIQUE)
  • tests (UUID, title, description, passing_threshold, time_limit, allow_back, is_active, is_versioned, created_by FK)
  • test_versions (UUID, test_id FK, version, is_active; миграция 002 добавляет parent_id + частичный уникальный индекс «одна активная версия на цепочку»)
  • questions (UUID, test_version_id FK, text, question_order, has_multiple_answers)
  • answer_options (UUID, question_id FK, text, is_correct, option_order)
  • test_assignments (UUID, test_version_id FK, assigned_by FK, deadline DATE, max_attempts)
  • test_assignment_targets (UUID, assignment_id FK, target_type 'department'|'user', target_id UUID)
  • test_attempts (UUID, test_version_id FK, user_id FK, attempt_number, status attempt_status, started_at, completed_at, correct_count, total_questions, passed; UNIQUE(test_version_id, user_id, attempt_number))
  • user_answers (UUID, attempt_id FK, question_id FK, selected_options UUID[])
  • migrations (служебная, имена применённых SQL)

Расширения: uuid-ossp. Кастомные типы: user_role, target_type, attempt_status.

3.2 Дополнительная — hr_bot_test (HR_DATABASE_URL, опционально)

Используется только на чтение через db/hrPool.js:

  • users — для входа (HR_AUTH=1): id, username, password_hash (Werkzeug), role
  • staff_membersid, fio, web_login
  • employees_departmentsstaff_id, department

3.3 Миграции

3 SQL-файла в backend/src/db/migrations/. Простой npm run migrate пишет в служебную таблицу migrations. В Flask эквивалент: Alembic или ручной runner на psycopg2. Файлы .sql можно переиспользовать как есть — никакого Node-специфичного синтаксиса в них нет.


4. Зависимости Node → Python (план замены)

Node-пакет Python-замена Комментарий
express, cors, cookie-parser Flask + flask-cors сессии встроены
multer Flask (request.files) + werkzeug.utils.secure_filename + tempfile лимит 10 МБ — MAX_CONTENT_LENGTH
dotenv python-dotenv (уже есть в flask_app/run.py)
pg psycopg2-binary (или psycopg[binary] v3)
bcryptjs bcrypt (pip install bcrypt) формат $2b$… совместим
jsonwebtoken pyjwt или flask-jwt-extended важен тот же алгоритм/секрет, чтобы старые cookie работали в переходный период
mammoth mammoth (PyPI: pip install mammoth) или python-docx API почти идентичен
pdf-parse pypdf (pip install pypdf) или pdfminer.six pypdf.PdfReader().pages[].extract_text()
fetch (LLM) httpx (рекомендую — есть timeout, async) или requests сохранить Authorization: Bearer …, response_format: json_object

Уже в tgFlaskForm есть готовоеwerkzeug.security.check_password_hash (бесплатно), Sentry, Jinja2-шаблоны, обмен с staff_members. Переиспользовать, если выберем сценарий «общий кабинет».


5. Переменные окружения (полный список)

Переменная Где читается Назначение Обязательная
NODE_ENV app.js, auth.js, featureFlags.js, db.js dev vs production да (нет → dev)
PORT server.js Express, по умолчанию 3001 нет
FRONTEND_URL app.js (CORS) разрешённый origin в prod в prod — да
DATABASE_URL или DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORD db/poolConfig.js основная БД clinic_tests да
DB_POOL_MAX, DB_IDLE_TIMEOUT, DB_CONNECTION_TIMEOUT db/poolConfig.js пул нет
HR_DATABASE_URL db/poolConfig.js (getHrPoolConfig) hr_bot_test (read-only) при HR_AUTH=1 — да
HR_DB_POOL_MAX то же пул HR нет
HR_AUTH (1/true) config/authConstants.js вход по HR-логину нет
JWT_SECRET utils/auth.js подпись JWT да
JWT_EXPIRES_IN utils/auth.js дефолт 7d нет
DEEPSEEK_API_KEY services/llmClient.js LLM, приоритет №1 для AI/импорта — да
OPENAI_API_KEY то же LLM, приоритет №2 альтернатива
LLM_BASE_URL то же сменить хост (proxy / vLLM) нет
LLM_MODEL то же по умолчанию deepseek-chat или gpt-4o-mini нет
LLM_NO_JSON (1) то же отключить response_format: json_object (для моделей без поддержки) нет
CLINIC_ASSIGNMENT_ENABLED (1) config/featureFlags.js прод: включить assign в prod — да

В flask_app/.env нужно перенести те же ключи (имена можно сохранить, чтобы не плодить вариации).


6. Что вызывает фронтенд (карта зависимостей React → API)

Используется один тонкий клиент frontend/src/api.js: fetch с credentials: 'include', базовый путь — пустой (т.е. /api/... относительно текущего origin, что в dev резолвит Vite-proxy, а в prod — Nginx). Это значит:

  • Менять фронтенд при смене бэкенда не нужно, если новый сервис отвечает по тем же путям.
  • В dev сейчас vite.config.js проксирует /api на Express (localhost:3001). После переноса — заменить адрес/порт на Flask (см. flask_app/run.py, по умолчанию 3108).

Список путей (отсортирован по убыванию использований):

/api/auth/login
/api/auth/logout
/api/auth/me
/api/auth/dev/assignment-directory?q&department&clinic
/api/tests
/api/tests/:id/summary
/api/tests/:id/versions
/api/tests/:id/editor
/api/tests/:id/chain-info
/api/tests/:id/draft
/api/tests/:id/assign
/api/tests/:id/ai/generate-test
/api/tests/:id/ai/generate-question
/api/tests/:id (PATCH)
/api/tests/:id/versions/:vid/activate
/api/tests/:id/attempts
/api/tests/:id/attempts/start
/api/tests/:id/attempts/:aid/play
/api/tests/:id/attempts/:aid/review
/api/tests/:id/attempts/:aid/submit
/api/tests/import/document

7. Тесты, которые тянутся за собой

В backend/src/**/*.test.js (Node test runner):

  • apiSmoke.test.js — smoke-проверки HTTP.
  • services/testChainService.test.js
  • services/aiEditorService.test.js
  • services/documentGenService.test.js
  • services/documentExtractService.test.js
  • utils/werkzeugPassword.test.js
  • integration/v9card1.test.js (требует CLINIC_TESTS_INTEGRATION=1)

После переноса нужны их Python-аналоги (pytest). Часть (валидация LLM-формы, разбор Werkzeug) тривиально превращается в юниты.


8. Чего сейчас в flask_app/ НЕТ (чтобы не повторяться в Спринте 2)

flask_app/ содержит только: run.py, app/__init__.py/health и /), app/templates/, app/static/. Не реализовано ничего из перечня §1–§4. Это значит, что в Спринте 2 нужно с нуля поднять:

  1. Подключение к БД (минимум — основной пул clinic_tests).
  2. Декоратор аутентификации (cookie JWT) и эндпоинты /api/auth/*.
  3. Роуты /api/tests/* поэтапно — начать с read-only (list, summary, versions, editor, attempts, review), потом write (draft, activate, patch, assign, attempts/start/submit), потом AI/import.
  4. CORS (в dev совпадает с тем, что у Express).
  5. Запуск под Vite/Nginx — обновить proxy/upstream.

9. Критерии «можно удалять backend/» (Спринт 4)

  • ETL-скрипт HR_TG_Bot/tgFlaskForm/tools/migrate_clinic_tests_to_hr.py прогнан на копии прод, агрегаты совпадают (тесты, версии, вопросы, попытки) — затем прогон на проде в окне.
  • Все сценарии §1 (TestingWebApp) либо имеют рабочий аналог в cabinet/testing/, либо явно признаны не-MVP (см. §10).
  • Внутренние ссылки и закладки сотрудников переключены на URL общего кабинета (/cabinet/testing/...).
  • Документация (README.md, DEV_CONTOUR_USER_GUIDE.md, шаги/*) перестала ссылаться на backend/ и frontend/ TestingWebApp.
  • Резерв: ветка legacy/clinic-tests-node (или legacy/express-backend) зафиксирована перед удалением.

10. Gap-analysis: tgFlaskForm/cabinet/testing vs Express

Сверка проведена по фактическому коду HR_TG_Bot/tgFlaskForm/webApp/interfaces/testing/* и HR_TG_Bot/tgFlaskForm/db/queries/testing_queries.py на 2026-04-27. Колонка «Что нужно сделать» — это и есть остаток Спринта 2 из migration-to-tgflaskform.md.

10.1 Маппинг сущностей и ID

TestingWebApp (clinic_tests) tgFlaskForm (hr_bot_test, схема testing_*) Замечание
tests (UUID) testing_tests (Integer PK) UUID → integer; маппинг хранится в _clinic_tests_migration_map
test_versions (UUID, version, is_active, parent_id) testing_test_versions (Integer, version_number, is_active_version) — + passing_score_percent, time_limit_minutes, allow_back_navigation (нормализовано на версию) В Express часть полей лежала на tests (passing_threshold, time_limit, allow_back); ETL поднимает их на версию
questions (text, has_multiple_answers, question_order) testing_questions (question_text, question_type 'single'|'multiple', sort_order) has_multiple_answers: truequestion_type='multiple'
answer_options (text, is_correct, option_order) testing_answers (answer_text, is_correct, sort_order) прямой маппинг
users + users.staff_id staff_members.id напрямую Express держит «свою» строку users со ссылкой на staff_id; в HR — только staff_members
test_assignments + test_assignment_targets (target_type='user', target_id=UUID) testing_assignments (одна строка = одна пара тест×сотрудник, assigned_to=staff_id) Существенное расхождение модели — см. §10.3
test_attempts (привязан к test_version_id, user_id) testing_attempts (привязан к assignment_id + test_version_id, попытка от сотрудника) Если в clinic попытка не имеет связанного assignment — ETL создаёт синтетическое назначение (max_attempts=99)
user_answers (selected_options uuid[]) testing_attempt_answers (одна строка на каждый выбранный answer_id) Развёртка массива в N строк
(нет аналога) testing_settings (key-value), testing_head_positions (право назначения по должности) в Express не было — RBAC проще

10.2 Эндпоинты Express → роуты tgFlaskForm

# Express Соответствие в tgFlaskForm Статус Что сделать
1 POST /api/auth/login webApp/auth/* (общая сессия HR) есть аналог Express-сессии не переносим; пользователи входят как обычно в HR-кабинет
2 POST /api/auth/logout webApp/auth/*
3 GET /api/auth/me session + helpers._get_staff_id
4 GET /api/auth/dev/assignment-directory routes_assignments.assign_form (страница) + testing_get_employees_for_assignment На UI: добавить «Выбрать всех» (см. Спринт 3 React-кабинета), мульти-фильтры
5 GET /api/health webApp/__init__.py
6 GET /api/tests (каталог + hiddenByYou) routes_tests.test_list (по автору) + routes_passing.my_tests (по назначениям) нет «скрытые мной» отдельным разделом Добавить вкладку/фильтр «скрытые автором» в cabinet/testing/test_list.html
7 POST /api/tests (создать пустой) routes_tests.test_create (требует full payload + ≥7 вопросов) другой контракт Решить продуктово: оставить ограничение ≥7 либо разрешить «пустой тест» как в Express. Рекомендую разрешить пустой и валидировать только при публикации
8 GET /api/tests/:id/summary вшито в test_edit нет отдельной summary Добавить функцию testing_get_test_summary(test_id) если нужен лёгкий вариант для списка
9 GET /api/tests/:id/versions testing_get_test_for_edit отдаёт активную; история — внутри test_edit Вынести список версий в API/шаблон («История» в UX)
10 GET /api/tests/:id/editor routes_tests.test_edit + testing_get_test_for_edit сверить набор полей, отдаваемых шаблону, с тем что показывает React-редактор
11 POST /api/tests/:id/ai/generate-test (строгая shape: [{optionsCount, hasMultipleAnswers}], до 40 вопросов) routes_ai.ai_generate принимает topic, question_count (фикс. форма) gap Добавить вариант с явной формой (передавать массив optionsCount/hasMultipleAnswers); либо принять, что HR-кабинет генерирует «обобщённо»
12 POST /api/tests/:id/ai/generate-question (один вопрос: пусто=новый, текст=переформулировать) routes_ai.ai_improve (улучшить) + ai_distractors (варианты) gap Добавить ручку «один вопрос со строгой сеткой» (объединить improve + distractors под единый сценарий)
13 POST /api/tests/:id/versions/:vid/activate routes_tests.test_version_activate
14 PATCH /api/tests/:id (chainActive) routes_tests.test_toggle сверить семантику (toggle vs явный флаг)
15 POST /api/tests/:id/assign (массивы userIds/staffIds) routes_assignments.assign_submit (employee_ids = staff_id[]) совместимо
16 POST /api/tests/:id/draft (с авто-fork версии) routes_tests.test_update (тоже фаркает при has_attempts) контракт другой Сверить, что в HR-кабинете тоже работает «правка без попыток = на месте, после попыток = новая версия». В коде есть, но нужна проверка
17 POST /api/tests/:id/attempts/start routes_passing.start_attempt(assignment_id) другой ключ Привести к виду «start by test_id» или оставить как есть и в UI стартовать по assignment_id (логичнее)
18 GET /api/tests/:id/attempts routes_results.tracker (общая лента) — без фильтра по тесту нет per-test ленты Добавить фильтр test_id или отдельный URL /cabinet/testing/tests/:id/attempts
19 GET /api/tests/:id/attempts/:aid/review routes_results.result(attempt_id) сверить, что разбор показывает выбранные/верные варианты по каждому вопросу
20 GET /api/tests/:id/attempts/:aid/play routes_passing.take_test(attempt_id)
21 POST /api/tests/:id/attempts/:aid/submit routes_passing.finish_attempt(attempt_id) (+ save_answer) контракт другой Express отправляет все ответы пакетом; HR — пошагово (save_answer × N + finish). Это OK для UX (продолжить позже), но фронт работает иначе
22 POST /api/tests/import/document routes_import.import_document сверить mime-типы и сообщения об ошибках
23 GET /api/tests/:id/chain-info (hasAnyAttempt) вшито в testing_get_test_for_edit['has_attempts']

Итог по gap-analysis: 13 из 22 эндпоинтов закрыты «как есть» или близко; 4 требуют добавления (AI «один вопрос», AI «строгая сетка», per-test attempts, summary); 5 — расхождение контракта, надо принять решение или подровнять.

10.3 Расхождения, которые ETL уже обходит

  • Назначения на отдел: в clinic_tests.test_assignment_targets могут быть строки target_type='department'. ETL разворачивает их в N строк testing_assignments по списку сотрудников отдела на момент миграции (см. шапку tools/migrate_clinic_tests_to_hr.py).
  • Попытки без явного назначения: в Express test_attempts.user_id напрямую идёт от пользователя; в HR попытка обязательно имеет assignment_id. ETL для каждой пары (тест, сотрудник) создаёт синтетическое назначение max_attempts=99 и привязывает попытку к нему.
  • UUID → Integer: идемпотентный маппинг лежит в public._clinic_tests_migration_map (entity, old_uuid → new_id) — повторный прогон ETL безопасен.

10.4 Чего точно нет в tgFlaskForm и потребует доработки

  1. AI «один вопрос со строгой сеткой» (generate_or_rephrase_question из aiEditorService.js).
  2. AI «целый тест по shape» с проверкой optionsCount и hasMultipleAnswers для каждой строки.
  3. «Скрытые автором» цепочки как отдельный список на UI.
  4. /tests/:id/summary — лёгкая ручка для карточки в списке (если потребуется в новом UI).
  5. Per-test лента попыток (UI «Прохождения» в аккордеоне «История»).
  6. Мобильный UX из Спринта 3: аккордеоны «О тесте / Вопросы / История / Показ в каталоге», фикс-футер, икон-кнопка удаления варианта, «Выбрать всех» в назначении и т.д. В Этапе 1 это переносится в Jinja-шаблоны flask_app/app/templates/. В Этапе 2 уже готовые шаблоны переезжают в tgFlaskForm/webApp/templates/cabinet/testing/.

10.5 Что точно НЕ переносим

  • Свой JWT/cookie/bcrypt из backend/src/utils/auth.js и middleware/auth.js. Авторизация — через сессии общего кабинета.
  • Werkzeug-полифилл в Node (utils/werkzeugPassword.js). В Python это родная функция werkzeug.security.check_password_hash.
  • Свои миграции SQL из backend/src/db/migrations/. Целевая схема живёт в tgFlaskForm/db/models.py и сопутствующих DDL/Alembic.
  • flask_app/ в этом репозитории — каркас уже не нужен, в Спринте 4 решим: удалить или оставить как историческую заготовку.
  • backend/src/**/*.test.js (Node test runner). Их предметная нагрузка покрывается либо уже существующими тестами tgFlaskForm, либо новыми pytest-юнитами.