|
7 | 7 |
|
8 | 8 | Маршрут — путь к FXML для `Navigator.goTo(...)`. |
9 | 9 |
|
10 | | -| № | Название страницы | Маршрут (FXML) | Доступ | Назначение | |
11 | | -| --- | -------------------------------------------- | ---------------------------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------- | |
12 | | -| 1 | Вход (email + пароль) | `/fxml/LoginForm.fxml` | гость (публичная) | Аутентификация через `AuthService`, после успеха → №2 | |
13 | | -| 2 | Список ресурсов и сортировка (главная) | `/fxml/ResourceList.fxml` (переименовать из `Search.fxml`) | `USER` | Поиск/фильтр/сортировка, пагинация `offset/count` через `ResourceService.search` | |
14 | | -| 3 | Просмотр метаданных документа | `/fxml/ResourceDetails.fxml` (новая) | `USER` | Только чтение: `ResourceService.getById` → `title, description, createdAt, updatedAt, uuid` | |
15 | | -| 4 | Форма ресурса (создание/редактирование) | `/fxml/ResourceForm.fxml` (новая) | `MODER` | `create/update` через `ResourceService`; удаление — действием из №2/№3 с подтверждением, без отдельной страницы | |
16 | | -| 5 | Список пользователей | `/fxml/UserList.fxml` (новая) | `ADMIN` | Поиск/фильтр через `UsersService.search` + `UserFilter`; удаление действием из списка | |
17 | | -| 6 | Форма пользователя (создание/редактирование) | `/fxml/UserForm.fxml` (новая) | `ADMIN` | `create` через `RegisterData`, `update` через `UpdateUserData`, `getById` для предзаполнения | |
| 10 | +## Паттерн взаимодействия с API |
| 11 | + |
| 12 | +UI не ходит в HTTP напрямую. Контроллер вызывает сервис («middleware»): |
| 13 | +`Controller → services.getXService() → ApiClient → HTTP + Session (Bearer)`. |
| 14 | +Сервис кладёт пользователя/токены в `Session`, ошибки пробрасывает как |
| 15 | +`ServiceException`, контроллер показывает их через `Alert`. |
| 16 | +Пример: docs/examples/how-to-interact-with-api-from-ui-via-middleware.md. |
| 17 | + |
| 18 | +| № | Название страницы | Маршрут (FXML) | Доступ | Назначение | |
| 19 | +| --- | -------------------------------------------- | ----------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------- | |
| 20 | +| 1 | Вход (email + пароль) | `/fxml/LoginForm.fxml` | гость (публичная) | Аутентификация через `AuthService`, после успеха → №2 | |
| 21 | +| 2 | Список ресурсов и сортировка (главная) | `/fxml/ResourceList.fxml` | `USER` | Поиск/фильтр/сортировка, пагинация `offset/count` через `ResourceService.search` | |
| 22 | +| 3 | Просмотр метаданных документа | `/fxml/ResourceDetails.fxml` (новая) | `USER` | Только чтение: `ResourceService.getById` → `title, description, createdAt, updatedAt, uuid` | |
| 23 | +| 4 | Форма ресурса (создание/редактирование) | `/fxml/ResourceForm.fxml` (новая) | `MODER` | `create/update` через `ResourceService`; удаление — действием из №2/№3 с подтверждением, без отдельной страницы | |
| 24 | +| 5 | Список пользователей | `/fxml/UserList.fxml` (новая) | `ADMIN` | Поиск/фильтр через `UsersService.search` + `UserFilter`; удаление действием из списка | |
| 25 | +| 6 | Форма пользователя (создание/редактирование) | `/fxml/UserForm.fxml` (новая) | `ADMIN` | `create` через `RegisterData`, `update` через `UpdateUserData`, `getById` для предзаполнения | |
| 26 | + |
| 27 | +## Интеграции страниц с эндпоинтами (через сервисы) |
| 28 | + |
| 29 | +Базовый URL: `api.baseUrl` (`http://localhost:8080`). |
| 30 | + |
| 31 | +| № | Действие UI | Сервис (middleware) | ApiClient → HTTP | |
| 32 | +|---|-------------|---------------------|------------------| |
| 33 | +| 1 | Войти | `AuthService.login(email, password)` | `POST /api/v1/auth/login` (`LoginRequest`), затем `GET /api/v1/user/me`; токены → `Session` | |
| 34 | +| 1 | Выйти | `AuthService.logout()` | `POST /api/v1/auth/logout` (`RefreshTokenRequest`), затем `Session.clear()` | |
| 35 | +| 2 | Поиск/фильтр/сортировка ресурсов | `ResourceService.search(filter, offset, count)` | `GET /api/v1/resource?title=&description=&offset=&count=&sortBy=createdAt&sortDir=desc` | |
| 36 | +| 3 | Открыть метаданные | `ResourceService.getById(id)` | `GET /api/v1/resource/{id}` | |
| 37 | +| 3 | Список/скачать/загрузить/удалить файлы | `FileService.{uploadFile, downloadFile, deleteFile}` | `POST /api/v1/resource/{id}/file` (multipart), `GET /api/v1/resource/{id}/file/{fileId}`, `DELETE /api/v1/resource/{id}/file/{fileId}` | |
| 38 | +| 3 | Экспорт файлов ресурса | `ExportService.exportResourceFiles(id, dir)` | `GET /api/v1/admin/resource/{id}/files/export` (бинарный ответ → файл) | |
| 39 | +| 4 | Создать ресурс | `ResourceService.create(title, description)` | `POST /api/v1/resource` (`CreateResourceRequest`) | |
| 40 | +| 4 | Обновить ресурс | `ResourceService.update(id, title, description)` | `PUT /api/v1/resource/{id}` (`UpdateResourceRequest`) | |
| 41 | +| 4 | Удалить ресурс (из №2/№3) | `ResourceService.delete(id)` | `DELETE /api/v1/resource/{id}` | |
| 42 | +| 5 | Поиск/фильтр пользователей | `UsersService.search(filter, offset, count)` | `GET /api/v1/admin?email=&firstName=&lastName=&role=&enabled=&offset=&count=&sortBy=createdAt&sortDir=desc` | |
| 43 | +| 5 | Удалить пользователя (из списка) | `UsersService.delete(id)` | `DELETE /api/v1/admin/{id}` | |
| 44 | +| 5 | Экспорт пользователей/ресурсов | `ExportService.{exportUsers, exportResources}` | `GET /api/v1/admin/users/export`, `GET /api/v1/admin/resource/export` | |
| 45 | +| 6 | Создать пользователя | `UsersService.create(RegisterData)` | `POST /api/v1/admin` (`RegisterRequest`: email, password, role, firstName, lastName) | |
| 46 | +| 6 | Открыть пользователя для редактирования | `UsersService.getById(id)` | `GET /api/v1/admin/{id}` | |
| 47 | +| 6 | Обновить пользователя | `UsersService.update(id, UpdateUserData)` | `PUT /api/v1/admin/{id}` (`UpdateUserRequest`: email, enabled, role, firstName, lastName) | |
| 48 | + |
| 49 | +Примечания: |
| 50 | +* `ApiClient` при 401 (кроме `/api/v1/auth/*`) один раз обновляет токены через |
| 51 | + `POST /api/v1/auth/refresh` и повторяет запрос; если refresh нет/не удался — |
| 52 | + исходный ответ уходит в `ServiceException`. |
| 53 | +* Клиентская валидация (FormsFX + `validation/*Validator`) срабатывает до вызова |
| 54 | + сервиса; серверные ошибки (`ApiException` → `ServiceException`) показывает контроллер. |
18 | 55 |
|
19 | 56 | ## Примечания по реализации |
20 | 57 |
|
21 | | -* №1: упростить `LoginFormController` — удалить `firstName/lastName/thirdName`, оставить `email + password` + вызов `AuthService`/`LoginRequest` (сейчас там заглушка `authenticate()` с `Role.USER`). |
22 | | -* №2: переименование `Search.fxml → ResourceList.fxml` + переименование `SearchController → ResourceListController`; обновить `Program.INITIAL_PAGE_PATH` и `navigator.goTo("/fxml/Search.fxml")` в `LoginFormController`. |
| 58 | +* №1: ✅ готово — `LoginFormController` только email + пароль, вызов `AuthService`. |
| 59 | +* №2: ✅ переименование `Search.fxml → ResourceList.fxml` готово; наполнить список: |
| 60 | + `ResourceService.search` + пагинация, фильтры `ResourceFilter(title, description)`. |
23 | 61 | * SoC: списки (№2, №5) не содержат логики форм; формы (№4, №6) работают в двух режимах `create/edit` по наличию `uuid`, валидация через существующие `validation/*Validator`. |
24 | 62 | * Кнопки create/edit/delete рендерятся по роли из `Session.getCurrentUser().role`, а не отдельными страницами. |
| 63 | + |
| 64 | +## Интеграционные тесты (rough) |
| 65 | + |
| 66 | +Стек: JUnit5 + AssertJ уже есть; TestFX headless настроен в `maven-surefire-plugin` |
| 67 | +(`--enable-native-access=ALL-UNNAMED`). HTTP backend-а в тестах нет — ставим |
| 68 | +стаб: WireMock (или подменный `ApiClient`) + TestFX для UI-слоя. |
| 69 | + |
| 70 | +* IT-Auth (№1): мок `POST /api/v1/auth/login` → 200 с токенами + `GET /api/v1/user/me` → 200 `UserDto`; |
| 71 | + проверить: `Session` заполнен, переход на `ResourceList`; кейс 401 → `ServiceException`, показан `Alert`, перехода нет. |
| 72 | +* IT-ResourceList (№2): мок `GET /api/v1/resource?...` → список `ResourceDto`; |
| 73 | + проверить: таблица/список отрисован, пагинация меняет `offset/count` в запросе; |
| 74 | + кейс 403 для `USER` без прав — сообщение об ошибке, без падения. |
| 75 | +* IT-ResourceDetails (№3): мок `GET /api/v1/resource/{id}` → 200 и 404; |
| 76 | + проверить: поля метаданных показаны; 404 → `Alert` «не найден». |
| 77 | +* IT-ResourceForm (№4): мок `POST /api/v1/resource` → 201 и `PUT /api/v1/resource/{id}` → 200; |
| 78 | + проверить: create/edit режимы по наличию `uuid`; `DELETE /api/v1/resource/{id}` после подтверждения. |
| 79 | +* IT-UserList/UserForm (№5–6, ADMIN): мок `GET /api/v1/admin?...` → список, |
| 80 | + `POST /api/v1/admin` → 201, `PUT /api/v1/admin/{id}` → 200, `DELETE` → 204; |
| 81 | + проверить: фильтр `email/role/enabled` уходит в query params; создание требует все поля `RegisterData`. |
| 82 | +* IT-Files/Export (№3/№5): мок multipart `POST .../file` → 201 `FileDto`, |
| 83 | + `GET .../file/{fileId}` → бинарник, `GET .../export` → бинарник; |
| 84 | + проверить: файл сохранён в `destinationDir`, имя взято из `Content-Disposition`. |
| 85 | +* IT-Refresh: мок защищённого `GET` → 401, затем `POST /api/v1/auth/refresh` → 200, |
| 86 | + повторный `GET` → 200; проверить: запрос повторён с новым Bearer без участия UI. |
0 commit comments