From 82d46aef718c6cd346c08b2840960c244fd3900e Mon Sep 17 00:00:00 2001 From: Vladimir Valekzhanin Date: Tue, 6 Oct 2026 16:48:37 +0300 Subject: [PATCH 01/10] feat(resource): add resource details page (read-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read-only страница №3: метаданные ресурса + таблица файлов со скачиванием, кнопка Назад. Подготовка: Navigator.goTo с Consumer-колбэком, List в Resource, hasRole по иерархии USER filesTable; + @FXML private TableColumn nameColumn; + @FXML private TableColumn sizeColumn; + @FXML private TableColumn contentTypeColumn; + @FXML private TableColumn downloadColumn; + @FXML private Button backButton; + + private UUID resourceId; + + @FXML + private void initialize() { + hideError(); + nameColumn.setCellValueFactory(new PropertyValueFactory<>("name")); + sizeColumn.setCellValueFactory(new PropertyValueFactory<>("size")); + contentTypeColumn.setCellValueFactory(new PropertyValueFactory<>("contentType")); + downloadColumn.setCellFactory(col -> new DownloadCell(this::onDownload)); + } + + public void setResourceId(UUID resourceId) { + this.resourceId = resourceId; + } + + public void load() { + hideError(); + try { + showResource(services.getResourceService().getById(resourceId)); + } catch (ServiceException e) { + showError(e.getMessage()); + } + } + + @FXML + private void onBack(ActionEvent event) { + navigator.goTo("/fxml/ResourceList.fxml"); + } + + private void onDownload(FileInfo file) { + File dir = chooseDirectory(backButton.getScene().getWindow()); + if (dir == null) { + return; + } + try { + Path saved = services.getFileService().downloadFile(resourceId, file.getUuid(), dir.toPath()); + statusLabel.setText("Сохранено: " + saved.getFileName()); + } catch (ServiceException e) { + showError(e.getMessage()); + } + } + + protected File chooseDirectory(Window owner) { + return new DirectoryChooser().showDialog(owner); + } + + private void showResource(Resource resource) { + titleLabel.setText(resource.getTitle()); + descriptionLabel.setText(resource.getDescription()); + createdLabel.setText(formatDateTime(resource.getCreatedAt())); + updatedLabel.setText(formatDateTime(resource.getUpdatedAt())); + uuidLabel.setText(resource.getUuid() == null ? "" : resource.getUuid().toString()); + List files = resource.getFiles() == null ? List.of() : resource.getFiles(); + filesTable.getItems().setAll(files); + } + + private static String formatDateTime(LocalDateTime value) { + return value == null ? "" : DATE_TIME_FORMAT.format(value); + } + + private void showError(String message) { + errorLabel.setText(message); + errorLabel.setVisible(true); + errorLabel.setManaged(true); + } + + private void hideError() { + errorLabel.setText(""); + errorLabel.setVisible(false); + errorLabel.setManaged(false); + } + + private static final class DownloadCell extends TableCell { + private final Button button = new Button("Скачать"); + + DownloadCell(Consumer onDownload) { + button.getStyleClass().setAll("btn", "btn-default"); + button.setOnAction(event -> onDownload.accept(getTableView().getItems().get(getIndex()))); + } + + @Override + protected void updateItem(Void item, boolean empty) { + super.updateItem(item, empty); + setGraphic(empty ? null : button); + } + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/entities/Resource.java b/src/main/java/com/DesktopApplicationClientJava/entities/Resource.java index 8606d49..44d5411 100644 --- a/src/main/java/com/DesktopApplicationClientJava/entities/Resource.java +++ b/src/main/java/com/DesktopApplicationClientJava/entities/Resource.java @@ -1,6 +1,7 @@ package com.DesktopApplicationClientJava.entities; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; @@ -17,4 +18,5 @@ public class Resource { private String description; private LocalDateTime createdAt; private LocalDateTime updatedAt; + private List files; } diff --git a/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java b/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java index 1ddcfb3..87d0a59 100644 --- a/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java +++ b/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java @@ -5,6 +5,7 @@ import com.DesktopApplicationClientJava.session.SessionAware; import com.DesktopApplicationClientJava.utils.ControllerWiring; import java.io.IOException; +import java.util.function.Consumer; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.stage.Stage; @@ -22,6 +23,10 @@ public Navigator(Stage stage, Session session, ServiceRegistry services) { } public void goTo(String fxmlFile) { + goTo(fxmlFile, controller -> {}); + } + + public void goTo(String fxmlFile, Consumer withController) { var url = Navigator.class.getResource(fxmlFile); if (url == null) { throw new IllegalArgumentException("FXML not found on classpath: " + fxmlFile); @@ -49,6 +54,7 @@ public void goTo(String fxmlFile) { if (controller instanceof NavigatorAware na) { na.setNavigator(this); } + withController.accept(controller); stage.getScene().setRoot(root); } catch (IOException e) { diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java index 382de28..6538915 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java @@ -55,7 +55,9 @@ public User getCurrentUser() { @Override public boolean hasRole(Role required) { User current = session.getCurrentUser(); - return current != null && current.getRole() == required; + return current != null + && current.getRole() != null + && level(current.getRole()) >= level(required); } @Override @@ -72,4 +74,12 @@ private User toEntity(UserDto dto) { return new User( dto.uuid(), dto.email(), dto.role(), dto.firstName(), dto.lastName(), dto.enabled()); } + + private static int level(Role role) { + return switch (role) { + case USER -> 0; + case MODER -> 1; + case ADMIN -> 2; + }; + } } diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java index 25d3725..b2473cf 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java @@ -6,6 +6,7 @@ import com.DesktopApplicationClientJava.Api.Dto.request.Resource.ResourceFilterRequest; import com.DesktopApplicationClientJava.Api.Dto.request.Resource.UpdateResourceRequest; import com.DesktopApplicationClientJava.Api.Dto.response.ResourceDto; +import com.DesktopApplicationClientJava.entities.FileInfo; import com.DesktopApplicationClientJava.entities.Resource; import com.DesktopApplicationClientJava.services.ResourceService; import com.DesktopApplicationClientJava.services.ServiceException; @@ -83,7 +84,13 @@ public void delete(UUID resourceId) { } private Resource toEntity(ResourceDto dto) { + List files = + dto.files() == null + ? List.of() + : dto.files().stream() + .map(f -> new FileInfo(f.uuid(), f.name(), f.contentType(), f.size())) + .toList(); return new Resource( - dto.uuid(), dto.title(), dto.description(), dto.createdAt(), dto.updatedAt()); + dto.uuid(), dto.title(), dto.description(), dto.createdAt(), dto.updatedAt(), files); } } diff --git a/src/main/resources/fxml/ResourceDetails.fxml b/src/main/resources/fxml/ResourceDetails.fxml new file mode 100644 index 0000000..395b680 --- /dev/null +++ b/src/main/resources/fxml/ResourceDetails.fxml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + +