Skip to content

Commit f264c80

Browse files
committed
docs: актуализировать спецификацию страниц и переименовать в pages.md
1 parent dd8271b commit f264c80

4 files changed

Lines changed: 139 additions & 91 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
description: Create an issue, create a branch, commit changes, create a pr
3+
agent: build
4+
---
5+
6+
# issue-branch-commit-pr
7+
8+
## Workflow
9+
10+
1. Create an issue.
11+
2. Check what branch you are on. If you are on a branch other than main, stash all changes, checkout to main, pull latest changes (`--ff-only`), and unstash changes. If no conflicts, create a new branch.
12+
3. Commit any unstaged and staged files.
13+
4. create a pr, link it to the issue, set appropriate labels, assignees (by default - the user) and reviewers.

‎.opencode/opencode.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "https://opencode.ai/config.json",
3+
"commands": {
4+
// This becomes the name of the command
5+
"issue-branch-commit-pr": {
6+
"template": "Create an issue, create a branch, commit changes, create a pr.",
7+
"agent": "build",
8+
}
9+
}
10+
}

‎docs/pages.md‎

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Страницы приложения
2+
3+
## RBAC
4+
5+
Иерархия: `USER <- MODER <- ADMIN` (старшая наследует права младшей).
6+
Доступ `USER` означает `USER, MODER, ADMIN`; `MODER` означает `MODER, ADMIN`.
7+
8+
Маршрут — путь к FXML для `Navigator.goTo(...)`.
9+
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 | @vladcheck | ✅ готово |
21+
| 2 | Список ресурсов и сортировка (главная) | `/fxml/ResourceList.fxml` | `USER` | Поиск по `title`, сортировка, пагинация `offset/count` (`PAGE_SIZE=20`) через `ResourceService.search` | @miyagi18 | ⚠️ частично (см. DOD №2) |
22+
| 3 | Просмотр метаданных ресурса | `/fxml/ResourceDetails.fxml` | `USER` | Только чтение: `ResourceService.getById` → `title, description, createdAt, updatedAt, uuid`; таблица файлов (имя/размер/тип + «Скачать») | @vladcheck | ⚠️ частично (см. DOD №3) |
23+
| 4 | Форма ресурса (создание/редактирование) | `/fxml/ResourceForm.fxml` | `MODER` | `create/update` через `ResourceService`; удаление — действием из №2/№3 с подтверждением, без отдельной страницы | @miyagi18 | ⚠️ частично (см. DOD №4) |
24+
| 5 | Список пользователей | `/fxml/UserList.fxml` (нет) | `ADMIN` | Поиск/фильтр через `UsersService.search` + `UserFilter`; удаление действием из списка | @deatheh | ❌ нет (сервис готов, UI нет) |
25+
| 6 | Форма пользователя (создание/редактирование) | `/fxml/UserForm.fxml` (нет) | `ADMIN` | `create` через `RegisterData`, `update` через `UpdateUserData`, `getById` для предзаполнения | @deatheh | ❌ нет (сервис готов, UI нет) |
26+
27+
## DOD для ⚠️-страниц
28+
29+
### №2 — Список ресурсов
30+
31+
* Есть: `ResourceListController` + `ResourceList.fxml`: поле поиска по `title`, `ComboBox` сортировки (4 опции: `createdAt desc/asc`, `title asc/desc`), таблица (`title, description, createdAt, updatedAt`), пагинация `offset/count` с `PAGE_SIZE=20`, переход дабл-кликом в №3 через `Navigator.goTo(path, c -> setResourceId(uuid))`, кнопка «+ Создать документ» только для `MODER/ADMIN`.
32+
* Нет: поля фильтра по `description` в UI нет (`ResourceFilter.description` всегда `null`); кнопки удаления в списке нет.
33+
* Готово когда: заведено поле `description` (или зафиксировано решение «искать только по title») и заведена кнопка удаления с подтверждением (см. DOD №4).
34+
35+
### №3 — Просмотр метаданных ресурса
36+
37+
* Есть: `ResourceDetailsController` (read-only): метаданные + таблица файлов (`name, size, contentType` + колонка «Скачать»), скачивание через `FileService.downloadFile` (`DirectoryChooser`, имя из `Content-Disposition`, fallback `fileUuid`), вход из №2 через `setResourceId(uuid)` + `load()`, кнопка «Редактировать» только для `MODER/ADMIN` (переход в №4).
38+
* Нет: кнопок загрузки/удаления файлов в UI нет; кнопки экспорта архива файлов нет.
39+
* Готово когда: заведены upload/delete (`FileService.{uploadFile, deleteFile}` уже есть) и экспорт (`ExportService.exportResourceFiles` уже есть) либо зафиксировано решение «в №3 только скачивание».
40+
41+
### №4 — Форма ресурса
42+
43+
* Есть: `ResourceFormController` + `ResourceForm.fxml`: режимы `create/edit` по наличию `resourceId`, предзаполнение через `getById`, ручная проверка `title` (`обязательно, 3–255 символов`, пустой `description` → `null`), сохранение через `create/update`, отмена → №2.
44+
* Нет: кнопки удаления в №2/№3/№4 нет, диалога подтверждения нет (`ResourceService.delete` реализован, UI не заведено).
45+
* Готово когда: удаление вызывается действием из №2/№3 с диалогом подтверждения, без отдельной страницы.
46+
47+
## Интеграции страниц с эндпоинтами (через сервисы)
48+
49+
Базовый URL: `api.baseUrl` (`http://localhost:8080`, см. `src/main/resources/api.properties`).
50+
51+
| № | Действие UI | Сервис (middleware) | ApiClient → HTTP |
52+
| --- | --------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
53+
| 1 | Войти | `AuthService.login(email, password)` | `POST /api/v1/auth/login` (`LoginRequest`), затем `GET /api/v1/user/me`; токены → `Session` |
54+
| 1 | Выйти | `AuthService.logout()` | `POST /api/v1/auth/logout` (`RefreshTokenRequest`), затем `Session.clear()` |
55+
| 2 | Поиск/сортировка ресурсов (только UI-факт: поиск по `title`) | `ResourceService.search(filter, offset, count[, sortBy, sortDir])` | `GET /api/v1/resource?title=&description=&offset=&count=&sortBy=&sortDir=` (UI шлёт только `title`; `description` поддерживается сервисом, из UI всегда `null`; сортировка `createdAt/title asc/desc`) |
56+
| 3 | Открыть метаданные | `ResourceService.getById(id)` | `GET /api/v1/resource/{id}` |
57+
| 3 | Скачать файл (UI-факт) | `FileService.downloadFile` | `GET /api/v1/resource/{id}/file/{fileId}` (имя из `Content-Disposition`) |
58+
| 3 | Загрузить/удалить файл (сервис готов, UI не заведено) | `FileService.{uploadFile, deleteFile}` | `POST /api/v1/resource/{id}/file` (multipart), `DELETE /api/v1/resource/{id}/file/{fileId}` |
59+
| 3 | Экспорт файлов ресурса (сервис готов, UI не заведено) | `ExportService.exportResourceFiles(id, dir)` | `GET /api/v1/admin/resource/{id}/files/export` (бинарный ответ → файл `files-{uuid}.xlsx`) |
60+
| 4 | Создать ресурс | `ResourceService.create(title, description)` | `POST /api/v1/resource` (`CreateResourceRequest`) |
61+
| 4 | Обновить ресурс | `ResourceService.update(id, title, description)` | `PUT /api/v1/resource/{id}` (`UpdateResourceRequest`) |
62+
| 4 | Удалить ресурс из №2/№3 (сервис готов, UI не заведено) | `ResourceService.delete(id)` | `DELETE /api/v1/resource/{id}` |
63+
| 5 | Поиск/фильтр пользователей (сервис готов, UI не заведено) | `UsersService.search(filter, offset, count)` | `GET /api/v1/admin?email=&firstName=&lastName=&role=&enabled=&offset=&count=&sortBy=createdAt&sortDir=desc` (сортировка захардкожена в `UsersServiceImpl`) |
64+
| 5 | Удалить пользователя (сервис готов, UI не заведено) | `UsersService.delete(id)` | `DELETE /api/v1/admin/{id}` |
65+
| 5 | Экспорт пользователей/ресурсов (сервис готов, UI не заведено) | `ExportService.{exportUsers, exportResources}` | `GET /api/v1/admin/users/export` (→ `users.xlsx`), `GET /api/v1/admin/resource/export` (→ `resources.xlsx`) |
66+
| 6 | Создать пользователя (сервис готов, UI не заведено) | `UsersService.create(RegisterData)` | `POST /api/v1/admin` (`RegisterRequest`: email, password, role, firstName, lastName) |
67+
| 6 | Открыть пользователя для редактирования (сервис готов, UI не заведено) | `UsersService.getById(id)` | `GET /api/v1/admin/{id}` |
68+
| 6 | Обновить пользователя (сервис готов, UI не заведено) | `UsersService.update(id, UpdateUserData)` | `PUT /api/v1/admin/{id}` (`UpdateUserRequest`: email, enabled, role, firstName, lastName) |
69+
70+
Примечания:
71+
* `ApiClient` при 401 (кроме `/api/v1/auth/*`) один раз обновляет токены через
72+
`POST /api/v1/auth/refresh` и повторяет запрос; если refresh нет/не удался —
73+
исходный ответ уходит в `ServiceException`.
74+
* Серверные ошибки (`ApiException` → `ServiceException`) показывает контроллер;
75+
проверка ввода выполняется до вызова сервиса (см. «Примечания по реализации»).
76+
77+
## Примечания по реализации
78+
79+
* №1: ✅ готово — `LoginFormController` только email + пароль: проверка непустоты + `EmailValidator.validate`, вызов `AuthService.login`, после успеха → №2.
80+
* №2: переименование `Search.fxml → ResourceList.fxml` готово; список наполнен: `ResourceService.search` + пагинация `PAGE_SIZE=20`, фильтр `ResourceFilter` (факт: из UI заполняется только `title`).
81+
* №3: частично — `ResourceDetailsController` (read-only): метаданные + таблица файлов
82+
со скачиванием (`FileService.downloadFile`), вход из №2 — `Navigator.goTo(path, c -> ...)`
83+
с `setResourceId(uuid)` + `load()`. Upload/delete/экспорт не заведены.
84+
* №4: частично — `ResourceFormController` в режимах `create/edit` по наличию `resourceId`
85+
(предзаполнение через `getById`); проверка `title` вручную (`3–255`), не через `validation/*Validator`.
86+
* №5–№6: ❌ UI нет (`UserList.fxml`/`UserForm.fxml`, контроллеров нет). Сервисный слой готов:
87+
`UsersServiceImpl` + `ApiClient` (`getUsersByFilters/createUser/getUserById/updateUser/deleteUser`).
88+
* Роли: `AuthServiceImpl.hasRole` сравнивает по уровню (`USER=0 < MODER=1 < ADMIN=2`),
89+
поэтому `isModerator()` возвращает true и для `ADMIN` — соответствует RBAC выше.
90+
* SoC: списки (№2) не содержат логики форм; форма №4 работает в двух режимах `create/edit` по наличию `uuid`.
91+
* Кнопки: `create` (№2) и `edit` (№3) рендерятся по роли (`isAdmin() || isModerator()` из `Session.getCurrentUser().role`), а не отдельными страницами. Кнопки `delete` не рендерятся нигде.
92+
* Проверка ввода (факт): №1 — `EmailValidator`; №4 — ручные проверки длины; классы
93+
`PasswordValidator/FirstNameValidator/LastNameValidator/ThirdNameValidator` на FormsFX существуют как задел под №6, в текущих страницах не используются.
94+
* Терминология: в доке используется «ресурс» per `GLOSSARY.md` (avoid «документ»).
95+
Факт расхождения: строки UI/FXML до сих пор говорят «документ» («Новый документ»,
96+
«Редактирование документа», «Архив документов», «Документов пока нет») — правится отдельно, не в этом доке.
97+
98+
## Тесты (факт)
99+
100+
Стек: JUnit5 + AssertJ; TestFX headless настроен в `maven-surefire-plugin`
101+
(`-Dtestfx.headless=true`, Monocle, `--enable-native-access=ALL-UNNAMED`,
102+
`-XX:+EnableDynamicAgentLoading`). WireMock в `pom.xml` отсутствует.
103+
104+
* Дефолтный прогон (без бэкенда): unit + UI на in-memory моках — `ServiceRegistry.mock`
105+
(`MockAuthService/MockUsersService/MockResourceService/MockFileService/MockExportService` + `MockStore`),
106+
`MOCK_DATA=true` / `INITIAL_PAGE_PATH` в `Program`. Покрыты: `LoginFormController`,
107+
`ResourceListController`, `ResourceDetailsController`, `ResourceFormController`, мапперы, валидаторы.
108+
* Live-интеграционные (`@Tag("integration")`, `src/test/.../ui/integration/`):
109+
исключены из дефолтного билда/CI через `excludedGroups` в surefire; требуют живой
110+
бэкенд `localhost:8080` и `.env` (`IT_ADMIN_EMAIL`, `IT_ADMIN_PASSWORD`, `dotenv.path`).
111+
Запуск явно: `mvn test -Dgroups=integration -DexcludedGroups=`.
112+
* Скачивание/экспорт (факт кода, без UI-кнопок): `ApiClient.downloadFile` сохраняет в
113+
`destinationDir`, имя из `Content-Disposition` (fallback `fileUuid`); `ExportServiceImpl`
114+
пишет `users.xlsx` / `resources.xlsx` / `files-{uuid}.xlsx`.
115+
* Refresh (факт кода): `ApiClient.executeWithRetry` при 401 (кроме `/api/v1/auth/*`)
116+
один раз делает `POST /api/v1/auth/refresh` и повторяет запрос с новым Bearer без участия UI.

0 commit comments

Comments
 (0)