diff --git a/pom.xml b/pom.xml index 3bf62e9..487a448 100644 --- a/pom.xml +++ b/pom.xml @@ -26,14 +26,12 @@ com.fasterxml.jackson.core jackson-databind - 2.18.11 + 2.18.8 - - com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.17.0 + 2.18.8 org.testfx diff --git a/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java b/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java index 66afc37..83425e8 100644 --- a/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java +++ b/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java @@ -13,6 +13,7 @@ import com.DesktopApplicationClientJava.Api.Dto.response.Jwt.JwtTokenResponse; import com.DesktopApplicationClientJava.Api.Dto.response.ResourceDto; import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; +import com.DesktopApplicationClientJava.session.Session; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.io.ByteArrayOutputStream; @@ -27,126 +28,129 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; public class ApiClient { + private final HttpClient httpClient; private final ObjectMapper objectMapper; - private final String baseUrl; - - private String accessToken; - private String refreshToken; + private final Session session; - public ApiClient(String baseUrl) { - this.baseUrl = baseUrl; - this.httpClient = HttpClient.newHttpClient(); + public ApiClient(Session session) { + this.session = session; + this.httpClient = + HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(ApiConfig.connectTimeoutMs())) + .build(); this.objectMapper = new ObjectMapper(); this.objectMapper.registerModule(new JavaTimeModule()); } - // Вход в систему + // ======================== AUTH ======================== + public JwtTokenResponse login(LoginRequest request) throws IOException, InterruptedException { - String jsonBody = objectMapper.writeValueAsString(request); + String json = objectMapper.writeValueAsString(request); HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/auth/login")) + .uri(uri(ApiConfig.pathLogin())) .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = send(httpRequest); checkError(response); - JwtTokenResponse tokenResponse = parseResponse(response, JwtTokenResponse.class); - this.accessToken = tokenResponse.accessToken(); - this.refreshToken = tokenResponse.refreshToken(); - return tokenResponse; + JwtTokenResponse tokens = parseResponse(response, JwtTokenResponse.class); + session.setAccessToken(tokens.accessToken()); + session.setRefreshToken(tokens.refreshToken()); + return tokens; } public JwtTokenResponse refresh() throws IOException, InterruptedException { refreshInternal(); - return new JwtTokenResponse(accessToken, refreshToken); + return new JwtTokenResponse(session.getAccessToken(), session.getRefreshToken()); } private void refreshInternal() throws IOException, InterruptedException { - if (refreshToken == null) { + String currentRefresh = session.getRefreshToken(); + if (currentRefresh == null) { throw new ApiException(new ErrorResponse("Ошибка: нет refresh токена", 401)); } - String jsonBody = objectMapper.writeValueAsString(new RefreshTokenRequest(refreshToken)); + String json = objectMapper.writeValueAsString(new RefreshTokenRequest(currentRefresh)); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/auth/refresh")) + .uri(uri(ApiConfig.pathRefresh())) .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() >= 400) { throw new ApiException( new ErrorResponse("Refresh failed: " + response.body(), response.statusCode())); } JwtTokenResponse tokens = objectMapper.readValue(response.body(), JwtTokenResponse.class); - this.accessToken = tokens.accessToken(); - this.refreshToken = tokens.refreshToken(); + session.setAccessToken(tokens.accessToken()); + session.setRefreshToken(tokens.refreshToken()); } - // Выход из системы public void logout() throws IOException, InterruptedException { - if (accessToken == null) { - return; - } - String jsonBody = objectMapper.writeValueAsString(new RefreshTokenRequest(refreshToken)); + String access = session.getAccessToken(); + if (access == null) return; + + String json = + objectMapper.writeValueAsString(new RefreshTokenRequest(session.getRefreshToken())); HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/auth/logout")) + .uri(uri(ApiConfig.pathLogout())) .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .header("Authorization", "Bearer " + access) + .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = send(httpRequest); checkError(response); - this.accessToken = null; - this.refreshToken = null; + session.clear(); } - // Получение информации пользователя о себе public UserDto getMe() throws IOException, InterruptedException { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/user/me")) - .header("Authorization", "Bearer " + accessToken) + .uri(uri(ApiConfig.pathMe())) + .header("Authorization", "Bearer " + session.getAccessToken()) .GET() .build(); - HttpResponse response = send(httpRequest); checkError(response); return parseResponse(response, UserDto.class); } - // Работа с Ресурсом(Документом) + // ======================== RESOURCE ======================== + public ResourceDto createResource(CreateResourceRequest request) throws IOException, InterruptedException { - String jsonBody = objectMapper.writeValueAsString(request); + String json = objectMapper.writeValueAsString(request); HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/resource")) + .uri(uri(ApiConfig.pathResource())) .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .header("Authorization", "Bearer " + session.getAccessToken()) + .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = send(httpRequest); checkError(response); return parseResponse(response, ResourceDto.class); } - // Поиск ресурса по Id public ResourceDto findResourceById(UUID id) throws IOException, InterruptedException { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/resource/" + id)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) + .uri(uri(ApiConfig.pathResource() + "/" + id)) + .header("Authorization", "Bearer " + session.getAccessToken()) .GET() .build(); HttpResponse response = send(httpRequest); @@ -154,7 +158,6 @@ public ResourceDto findResourceById(UUID id) throws IOException, InterruptedExce return parseResponse(response, ResourceDto.class); } - // Поиск ресурса по параметрам public List findResourcesByFilters( ResourceFilterRequest filter, Long offset, Long count, String sortBy, String sortDir) throws IOException, InterruptedException { @@ -164,80 +167,76 @@ public List findResourcesByFilters( params.add("title=" + encode(filter.title())); if (filter.description() != null && !filter.description().isBlank()) params.add("description=" + encode(filter.description())); - // createdAt/updatedAt пока не отправляем — при необходимости добавим формат } if (offset != null) params.add("offset=" + offset); if (count != null) params.add("count=" + count); - if (sortBy != null) params.add("sortBy=" + sortBy); - if (sortDir != null) params.add("sortDir=" + sortDir); + if (sortBy != null && !sortBy.isBlank()) params.add("sortBy=" + encode(sortBy)); + if (sortDir != null && !sortDir.isBlank()) params.add("sortDir=" + encode(sortDir)); + String url = - baseUrl + "/api/v1/resource" + (params.isEmpty() ? "" : "?" + String.join("&", params)); + ApiConfig.baseUrl() + + ApiConfig.pathResource() + + (params.isEmpty() ? "" : "?" + String.join("&", params)); HttpRequest httpRequest = HttpRequest.newBuilder() .uri(URI.create(url)) - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + session.getAccessToken()) .GET() .build(); HttpResponse response = send(httpRequest); checkError(response); - ResourceDto[] arr = objectMapper.readValue(response.body(), ResourceDto[].class); return Arrays.asList(arr); } - // Обновление ресурса public ResourceDto updateResource(UUID uuid, UpdateResourceRequest request) throws IOException, InterruptedException { - String jsonBody = objectMapper.writeValueAsString(request); + String json = objectMapper.writeValueAsString(request); HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/resource/" + uuid)) + .uri(uri(ApiConfig.pathResource() + "/" + uuid)) .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) - .PUT(HttpRequest.BodyPublishers.ofString(jsonBody)) + .header("Authorization", "Bearer " + session.getAccessToken()) + .PUT(HttpRequest.BodyPublishers.ofString(json)) .build(); - HttpResponse response = send(httpRequest); checkError(response); return parseResponse(response, ResourceDto.class); } - // Удаление ресурса public void deleteResource(UUID uuid) throws IOException, InterruptedException { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/resource/" + uuid)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) + .uri(uri(ApiConfig.pathResource() + "/" + uuid)) + .header("Authorization", "Bearer " + session.getAccessToken()) .DELETE() .build(); HttpResponse response = send(httpRequest); checkError(response); } - // Создание пользователя + // ======================== ADMIN ======================== + public UserDto createUser(RegisterRequest request) throws IOException, InterruptedException { - String jsonBody = objectMapper.writeValueAsString(request); + String json = objectMapper.writeValueAsString(request); HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/admin")) + .uri(uri(ApiConfig.pathAdmin())) .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) - .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .header("Authorization", "Bearer " + session.getAccessToken()) + .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = send(httpRequest); checkError(response); return parseResponse(response, UserDto.class); } - // Поиск пользователя по ID public UserDto getUserById(UUID uuid) throws IOException, InterruptedException { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/admin/" + uuid)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) + .uri(uri(ApiConfig.pathAdmin() + "/" + uuid)) + .header("Authorization", "Bearer " + session.getAccessToken()) .GET() .build(); HttpResponse response = send(httpRequest); @@ -245,7 +244,6 @@ public UserDto getUserById(UUID uuid) throws IOException, InterruptedException { return parseResponse(response, UserDto.class); } - // Поиск пользователя по параметрам public List getUsersByFilters( UserFilterRequest filter, Long offset, Long count, String sortBy, String sortDir) throws IOException, InterruptedException { @@ -260,57 +258,56 @@ public List getUsersByFilters( if (filter.role() != null) params.add("role=" + encode(filter.role().name())); if (filter.enabled() != null) params.add("enabled=" + filter.enabled()); } - if (offset != null) params.add("offset=" + offset); if (count != null) params.add("count=" + count); - if (sortBy != null) params.add("sortBy=" + encode(sortBy)); - if (sortDir != null) params.add("sortDir=" + encode(sortDir)); + if (sortBy != null && !sortBy.isBlank()) params.add("sortBy=" + encode(sortBy)); + if (sortDir != null && !sortDir.isBlank()) params.add("sortDir=" + encode(sortDir)); String url = - baseUrl + "/api/v1/admin" + (params.isEmpty() ? "" : "?" + String.join("&", params)); + ApiConfig.baseUrl() + + ApiConfig.pathAdmin() + + (params.isEmpty() ? "" : "?" + String.join("&", params)); HttpRequest httpRequest = HttpRequest.newBuilder() .uri(URI.create(url)) - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + session.getAccessToken()) .GET() .build(); - HttpResponse response = send(httpRequest); checkError(response); UserDto[] users = objectMapper.readValue(response.body(), UserDto[].class); return Arrays.asList(users); } - // Обновление пользователя public UserDto updateUser(UUID uuid, UpdateUserRequest request) throws IOException, InterruptedException { - String jsonBody = objectMapper.writeValueAsString(request); + String json = objectMapper.writeValueAsString(request); HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/admin/" + uuid)) + .uri(uri(ApiConfig.pathAdmin() + "/" + uuid)) .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) - .PUT(HttpRequest.BodyPublishers.ofString(jsonBody)) + .header("Authorization", "Bearer " + session.getAccessToken()) + .PUT(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = send(httpRequest); checkError(response); return parseResponse(response, UserDto.class); } - // Удаление пользователя public void deleteUser(UUID uuid) throws IOException, InterruptedException { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/admin/" + uuid)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) + .uri(uri(ApiConfig.pathAdmin() + "/" + uuid)) + .header("Authorization", "Bearer " + session.getAccessToken()) .DELETE() .build(); HttpResponse response = send(httpRequest); checkError(response); } + // ======================== FILES ======================== + public FileDto uploadFile(UUID resourceUuid, Path filePath) throws IOException, InterruptedException { String boundary = "----JavaPksBoundary" + System.currentTimeMillis(); @@ -320,7 +317,6 @@ public FileDto uploadFile(UUID resourceUuid, Path filePath) Optional.ofNullable(Files.probeContentType(filePath)).orElse("application/octet-stream"); ByteArrayOutputStream body = new ByteArrayOutputStream(); - body.write(("--" + boundary + "\r\n").getBytes(StandardCharsets.UTF_8)); body.write( ("Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n") @@ -331,9 +327,9 @@ public FileDto uploadFile(UUID resourceUuid, Path filePath) HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/resource/" + resourceUuid + "/file")) + .uri(uri(ApiConfig.pathResourceFiles(resourceUuid.toString()))) .header("Content-Type", "multipart/form-data; boundary=" + boundary) - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + session.getAccessToken()) .POST(HttpRequest.BodyPublishers.ofByteArray(body.toByteArray())) .build(); @@ -347,8 +343,8 @@ public Path downloadFile(UUID resourceUuid, UUID fileUuid, Path destinationDir) HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/resource/" + resourceUuid + "/file/" + fileUuid)) - .header("Authorization", "Bearer " + accessToken) + .uri(uri(ApiConfig.pathResourceFile(resourceUuid.toString(), fileUuid.toString()))) + .header("Authorization", "Bearer " + session.getAccessToken()) .GET() .build(); @@ -376,7 +372,6 @@ public Path downloadFile(UUID resourceUuid, UUID fileUuid, Path destinationDir) Files.createDirectories(destinationDir); Path target = destinationDir.resolve(filename); - try (InputStream in = response.body()) { Files.copy(in, target, java.nio.file.StandardCopyOption.REPLACE_EXISTING); } @@ -387,8 +382,8 @@ public void deleteFile(UUID resourceUuid, UUID fileUuid) throws IOException, InterruptedException { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + "/api/v1/resource/" + resourceUuid + "/file/" + fileUuid)) - .header("Authorization", "Bearer " + accessToken) + .uri(uri(ApiConfig.pathResourceFile(resourceUuid.toString(), fileUuid.toString()))) + .header("Authorization", "Bearer " + session.getAccessToken()) .DELETE() .build(); HttpResponse response = send(httpRequest); @@ -425,24 +420,25 @@ private static String parseFilename(String cd) { return null; } - // Выгрузка данных (пользователей, документов и их файлов) + // ======================== EXPORT ======================== + public byte[] exportUsers() throws IOException, InterruptedException { - return downloadBinary("/api/v1/admin/users/export"); + return downloadBinary(ApiConfig.pathExportUsers()); } public byte[] exportResources() throws IOException, InterruptedException { - return downloadBinary("/api/v1/admin/resource/export"); + return downloadBinary(ApiConfig.pathExportResources()); } public byte[] exportResourceFiles(UUID resourceId) throws IOException, InterruptedException { - return downloadBinary("/api/v1/admin/resource/" + resourceId + "/files/export"); + return downloadBinary(ApiConfig.pathExportResourceFiles(resourceId.toString())); } private byte[] downloadBinary(String path) throws IOException, InterruptedException { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(baseUrl + path)) - .header("Authorization", "Bearer " + accessToken) + .uri(uri(path)) + .header("Authorization", "Bearer " + session.getAccessToken()) .GET() .build(); @@ -463,11 +459,16 @@ private byte[] downloadBinary(String path) throws IOException, InterruptedExcept return response.body(); } + // ======================== HELPERS ======================== + + private URI uri(String path) { + return URI.create(ApiConfig.baseUrl() + path); + } + private HttpResponse send(HttpRequest request) throws IOException, InterruptedException { return executeWithRetry(request, HttpResponse.BodyHandlers.ofString()); } - // Проверка ошибок сервера private void checkError(HttpResponse response) { if (response.statusCode() < 400) return; try { @@ -481,11 +482,8 @@ private void checkError(HttpResponse response) { } } - // Парсер ответа private T parseResponse(HttpResponse response, Class clazz) throws IOException { - if (response.body() == null || response.body().isBlank()) { - return null; - } + if (response.body() == null || response.body().isBlank()) return null; return objectMapper.readValue(response.body(), clazz); } @@ -494,50 +492,24 @@ private HttpResponse executeWithRetry( throws IOException, InterruptedException { HttpResponse response = httpClient.send(request, handler); - if (response.statusCode() != 401) return response; - if (request.uri().getPath().startsWith("/api/v1/auth/")) return response; - - if (refreshToken == null) return response; + if (session.getRefreshToken() == null) return response; try { refreshInternal(); } catch (Exception e) { - // refresh не удался — отдаём оригинальный 401, вызывающий код разберётся return response; } - // повторяем исходный запрос с новым access-токеном HttpRequest retried = HttpRequest.newBuilder(request, (name, value) -> !name.equalsIgnoreCase("Authorization")) - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + session.getAccessToken()) .build(); - return httpClient.send(retried, handler); } private static String encode(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8); } - - public String getAccessToken() { - return accessToken; - } - - public String getRefreshToken() { - return refreshToken; - } - - public HttpClient getHttpClient() { - return httpClient; - } - - public ObjectMapper getObjectMapper() { - return objectMapper; - } - - public String getBaseUrl() { - return baseUrl; - } } diff --git a/src/main/java/com/DesktopApplicationClientJava/Api/ApiConfig.java b/src/main/java/com/DesktopApplicationClientJava/Api/ApiConfig.java index e434338..891095e 100644 --- a/src/main/java/com/DesktopApplicationClientJava/Api/ApiConfig.java +++ b/src/main/java/com/DesktopApplicationClientJava/Api/ApiConfig.java @@ -1,3 +1,90 @@ package com.DesktopApplicationClientJava.Api; -public class ApiConfig {} +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public final class ApiConfig { + + private static final Properties PROPS = new Properties(); + + static { + try (InputStream in = ApiConfig.class.getResourceAsStream("/api.properties")) { + if (in == null) { + throw new IllegalStateException("api.properties not found on classpath"); + } + PROPS.load(in); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + private ApiConfig() {} + + public static String baseUrl() { + return required("api.baseUrl"); + } + + public static long connectTimeoutMs() { + return Long.parseLong(required("api.connectTimeoutMs")); + } + + public static long readTimeoutMs() { + return Long.parseLong(required("api.readTimeoutMs")); + } + + public static String pathLogin() { + return required("api.path.login"); + } + + public static String pathRefresh() { + return required("api.path.refresh"); + } + + public static String pathLogout() { + return required("api.path.logout"); + } + + public static String pathMe() { + return required("api.path.me"); + } + + public static String pathResource() { + return required("api.path.resource"); + } + + public static String pathAdmin() { + return required("api.path.admin"); + } + + public static String pathExportUsers() { + return required("api.path.export.users"); + } + + public static String pathExportResources() { + return required("api.path.export.resources"); + } + + /** /api/v1/admin/resource/{uuid}/files/export */ + public static String pathExportResourceFiles(String resourceId) { + return pathAdmin() + "/resource/" + resourceId + "/files/export"; + } + + /** /api/v1/resource/{uuid}/file */ + public static String pathResourceFiles(String resourceId) { + return pathResource() + "/" + resourceId + "/file"; + } + + /** /api/v1/resource/{uuid}/file/{fileId} */ + public static String pathResourceFile(String resourceId, String fileId) { + return pathResource() + "/" + resourceId + "/file/" + fileId; + } + + private static String required(String key) { + String value = PROPS.getProperty(key); + if (value == null || value.isBlank()) { + throw new IllegalStateException("Missing config property: " + key); + } + return value; + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/Program.java b/src/main/java/com/DesktopApplicationClientJava/Program.java index c5bbe90..9ec2026 100644 --- a/src/main/java/com/DesktopApplicationClientJava/Program.java +++ b/src/main/java/com/DesktopApplicationClientJava/Program.java @@ -1,5 +1,8 @@ package com.DesktopApplicationClientJava; +import com.DesktopApplicationClientJava.Api.ApiClient; +import com.DesktopApplicationClientJava.navigation.Navigator; +import com.DesktopApplicationClientJava.services.ServiceRegistry; import com.DesktopApplicationClientJava.session.Session; import com.DesktopApplicationClientJava.utils.ControllerWiring; import java.io.IOException; @@ -10,29 +13,50 @@ import javafx.stage.Stage; public class Program extends Application { + private static final String INITIAL_PAGE_PATH = "/fxml/Search.fxml"; + private static final double WINDOW_WIDTH = 800.0; + private static final double WINDOW_HEIGHT = 600.0; + private final Session session = new Session(); - private static final double windowWidthPx = 400.0; - private static final double windowHeightPx = 400.0; + private final ApiClient apiClient = new ApiClient(session); + private final ServiceRegistry services = new ServiceRegistry(apiClient, session); + + public static void main(String[] args) { + launch(args); + } @Override public void start(Stage stage) throws IOException { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LoginForm.fxml")); + Navigator navigator = new Navigator(stage, session, services); + + FXMLLoader loader = new FXMLLoader(getClass().getResource(INITIAL_PAGE_PATH)); + loader.setControllerFactory( + clazz -> { + try { + Object controller = clazz.getDeclaredConstructor().newInstance(); + ControllerWiring.wire(controller, navigator, session, services); + return controller; + } catch (Exception e) { + throw new RuntimeException("Cannot create controller: " + clazz, e); + } + }); + Parent root = loader.load(); Scene scene = new Scene(root); loadStyleSheet(scene, "/styles.css"); - ControllerWiring.wire(loader.getController(), stage, session); - stage.setScene(scene); stage.setTitle("JavaFX Application"); - stage.setWidth(windowWidthPx); - stage.setHeight(windowHeightPx); + stage.setWidth(WINDOW_WIDTH); + stage.setHeight(WINDOW_HEIGHT); stage.centerOnScreen(); - stage.show(); } private void loadStyleSheet(Scene scene, String path) { - scene.getStylesheets().add(getClass().getResource(path).toExternalForm()); + var url = getClass().getResource(path); + if (url != null) { + scene.getStylesheets().add(url.toExternalForm()); + } } } diff --git a/src/main/java/com/DesktopApplicationClientJava/controllers/LoginFormController.java b/src/main/java/com/DesktopApplicationClientJava/controllers/LoginFormController.java index d8baf11..48e43eb 100644 --- a/src/main/java/com/DesktopApplicationClientJava/controllers/LoginFormController.java +++ b/src/main/java/com/DesktopApplicationClientJava/controllers/LoginFormController.java @@ -1,6 +1,7 @@ package com.DesktopApplicationClientJava.controllers; import com.DesktopApplicationClientJava.controllers.utils.Controller; +import com.DesktopApplicationClientJava.entities.Role; import com.DesktopApplicationClientJava.entities.User; import com.DesktopApplicationClientJava.utils.FieldError; import com.DesktopApplicationClientJava.validation.EmailValidator; @@ -9,6 +10,7 @@ import com.DesktopApplicationClientJava.validation.PasswordValidator; import com.DesktopApplicationClientJava.validation.ThirdNameValidator; import com.DesktopApplicationClientJava.validation.utils.Validator; +import java.util.UUID; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -103,6 +105,6 @@ private boolean isFieldValid(TextField field, Validator validator, FieldError er } private User authenticate(String firstName, String lastName, String email) { - return new User(firstName, lastName, email); + return new User(UUID.randomUUID(), email, Role.USER, firstName, lastName, true); } } diff --git a/src/main/java/com/DesktopApplicationClientJava/controllers/utils/Controller.java b/src/main/java/com/DesktopApplicationClientJava/controllers/utils/Controller.java index 34b2bc4..e16b9c7 100644 --- a/src/main/java/com/DesktopApplicationClientJava/controllers/utils/Controller.java +++ b/src/main/java/com/DesktopApplicationClientJava/controllers/utils/Controller.java @@ -2,18 +2,29 @@ import com.DesktopApplicationClientJava.navigation.Navigator; import com.DesktopApplicationClientJava.navigation.NavigatorAware; +import com.DesktopApplicationClientJava.services.ServiceRegistry; +import com.DesktopApplicationClientJava.services.ServicesAware; import com.DesktopApplicationClientJava.session.Session; import com.DesktopApplicationClientJava.session.SessionAware; -public abstract class Controller implements SessionAware, NavigatorAware { +public abstract class Controller implements SessionAware, NavigatorAware, ServicesAware { + protected Session session; protected Navigator navigator; + protected ServiceRegistry services; + @Override public void setSession(Session session) { this.session = session; } + @Override public void setNavigator(Navigator navigator) { this.navigator = navigator; } + + @Override + public void setServices(ServiceRegistry services) { + this.services = services; + } } diff --git a/src/main/java/com/DesktopApplicationClientJava/entities/FileInfo.java b/src/main/java/com/DesktopApplicationClientJava/entities/FileInfo.java new file mode 100644 index 0000000..5af1539 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/entities/FileInfo.java @@ -0,0 +1,18 @@ +package com.DesktopApplicationClientJava.entities; + +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class FileInfo { + private UUID uuid; + private String name; + private String contentType; + private Long size; +} diff --git a/src/main/java/com/DesktopApplicationClientJava/entities/Resource.java b/src/main/java/com/DesktopApplicationClientJava/entities/Resource.java new file mode 100644 index 0000000..8606d49 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/entities/Resource.java @@ -0,0 +1,20 @@ +package com.DesktopApplicationClientJava.entities; + +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Resource { + private UUID uuid; + private String title; + private String description; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/DesktopApplicationClientJava/entities/User.java b/src/main/java/com/DesktopApplicationClientJava/entities/User.java index dfc6d99..5999462 100644 --- a/src/main/java/com/DesktopApplicationClientJava/entities/User.java +++ b/src/main/java/com/DesktopApplicationClientJava/entities/User.java @@ -1,15 +1,20 @@ package com.DesktopApplicationClientJava.entities; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; -/** User */ -@AllArgsConstructor @Getter @Setter +@NoArgsConstructor +@AllArgsConstructor public class User { + private UUID uuid; + private String email; + private Role role; private String firstName; private String lastName; - private String email; + private Boolean enabled; } diff --git a/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java b/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java index 3f84fc7..1ddcfb3 100644 --- a/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java +++ b/src/main/java/com/DesktopApplicationClientJava/navigation/Navigator.java @@ -1,18 +1,25 @@ package com.DesktopApplicationClientJava.navigation; +import com.DesktopApplicationClientJava.services.ServiceRegistry; import com.DesktopApplicationClientJava.session.Session; import com.DesktopApplicationClientJava.session.SessionAware; +import com.DesktopApplicationClientJava.utils.ControllerWiring; import java.io.IOException; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.stage.Stage; -import lombok.AllArgsConstructor; /** Navigator */ -@AllArgsConstructor public class Navigator { private final Stage stage; private final Session session; + private final ServiceRegistry services; + + public Navigator(Stage stage, Session session, ServiceRegistry services) { + this.stage = stage; + this.session = session; + this.services = services; + } public void goTo(String fxmlFile) { var url = Navigator.class.getResource(fxmlFile); @@ -21,15 +28,28 @@ public void goTo(String fxmlFile) { } FXMLLoader loader = new FXMLLoader(url); + loader.setControllerFactory( + clazz -> { + try { + Object controller = clazz.getDeclaredConstructor().newInstance(); + ControllerWiring.wire(controller, this, session, services); + return controller; + } catch (Exception e) { + throw new RuntimeException("Cannot create controller: " + clazz, e); + } + }); + try { Parent root = loader.load(); - var controller = loader.getController(); + + Object controller = loader.getController(); if (controller instanceof SessionAware sa) { sa.setSession(session); } if (controller instanceof NavigatorAware na) { na.setNavigator(this); } + stage.getScene().setRoot(root); } catch (IOException e) { throw new RuntimeException("Failed to load FXML: " + fxmlFile, e); diff --git a/src/main/java/com/DesktopApplicationClientJava/services/AuthService.java b/src/main/java/com/DesktopApplicationClientJava/services/AuthService.java index 40667ff..e378a91 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/AuthService.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/AuthService.java @@ -1,3 +1,23 @@ package com.DesktopApplicationClientJava.services; -public class AuthService {} +import com.DesktopApplicationClientJava.entities.Role; +import com.DesktopApplicationClientJava.entities.User; + +public interface AuthService { + + /** Логин. При успехе user попадает в Session. */ + User login(String email, String password); + + /** Выход. Сбрасывает Session. */ + void logout(); + + /** Текущий пользователь из Session (или null). */ + User getCurrentUser(); + + /** Проверка роли текущего пользователя. */ + boolean hasRole(Role required); + + boolean isAdmin(); + + boolean isModerator(); +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/ExportService.java b/src/main/java/com/DesktopApplicationClientJava/services/ExportService.java new file mode 100644 index 0000000..03eb7ab --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/ExportService.java @@ -0,0 +1,13 @@ +package com.DesktopApplicationClientJava.services; + +import java.nio.file.Path; +import java.util.UUID; + +public interface ExportService { + + Path exportUsers(Path destinationDir); + + Path exportResources(Path destinationDir); + + Path exportResourceFiles(UUID resourceUuid, Path destinationDir); +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/FileService.java b/src/main/java/com/DesktopApplicationClientJava/services/FileService.java index 6f616cf..e655541 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/FileService.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/FileService.java @@ -1,3 +1,13 @@ package com.DesktopApplicationClientJava.services; -public class FileService {} +import com.DesktopApplicationClientJava.entities.FileInfo; +import java.nio.file.Path; +import java.util.UUID; + +public interface FileService { + FileInfo uploadFile(UUID resourceId, Path file); + + Path downloadFile(UUID resourceId, UUID fileId, Path destinationDir); + + void deleteFile(UUID resourceId, UUID fileId); +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/ResourceService.java b/src/main/java/com/DesktopApplicationClientJava/services/ResourceService.java index cabf91c..4075e5b 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/ResourceService.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/ResourceService.java @@ -1,3 +1,18 @@ package com.DesktopApplicationClientJava.services; -public class ResourceService {} +import com.DesktopApplicationClientJava.entities.Resource; +import com.DesktopApplicationClientJava.services.filter.ResourceFilter; +import java.util.List; +import java.util.UUID; + +public interface ResourceService { + List search(ResourceFilter filter, long offset, long count); + + Resource create(String title, String description); + + Resource getById(UUID resourceId); + + Resource update(UUID resourceId, String title, String description); + + void delete(UUID resourceId); +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/ServiceException.java b/src/main/java/com/DesktopApplicationClientJava/services/ServiceException.java new file mode 100644 index 0000000..07b5c3d --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/ServiceException.java @@ -0,0 +1,18 @@ +package com.DesktopApplicationClientJava.services; + +public class ServiceException extends RuntimeException { + private final int status; + + public ServiceException(String message, int status) { + super(message); + this.status = status; + } + + public ServiceException(String message) { + this(message, 0); + } + + public int getStatus() { + return status; + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/ServiceRegistry.java b/src/main/java/com/DesktopApplicationClientJava/services/ServiceRegistry.java new file mode 100644 index 0000000..e5d2d7c --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/ServiceRegistry.java @@ -0,0 +1,41 @@ +package com.DesktopApplicationClientJava.services; + +import com.DesktopApplicationClientJava.Api.ApiClient; +import com.DesktopApplicationClientJava.services.impl.*; +import com.DesktopApplicationClientJava.session.Session; + +public class ServiceRegistry { + private final AuthService authService; + private final UsersService usersService; + private final ResourceService resourceService; + private final FileService fileService; + private final ExportService exportService; + + public ServiceRegistry(ApiClient apiClient, Session session) { + this.authService = new AuthServiceImpl(apiClient, session); + this.usersService = new UsersServiceImpl(apiClient); + this.resourceService = new ResourceServiceImpl(apiClient); + this.fileService = new FileServiceImpl(apiClient); + this.exportService = new ExportServiceImpl(apiClient); + } + + public AuthService getAuthService() { + return authService; + } + + public ExportService getExportService() { + return exportService; + } + + public FileService getFileService() { + return fileService; + } + + public ResourceService getResourceService() { + return resourceService; + } + + public UsersService getUsersService() { + return usersService; + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/ServicesAware.java b/src/main/java/com/DesktopApplicationClientJava/services/ServicesAware.java new file mode 100644 index 0000000..4875b61 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/ServicesAware.java @@ -0,0 +1,5 @@ +package com.DesktopApplicationClientJava.services; + +public interface ServicesAware { + void setServices(ServiceRegistry services); +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/UsersService.java b/src/main/java/com/DesktopApplicationClientJava/services/UsersService.java index 018573e..5c346b9 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/UsersService.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/UsersService.java @@ -1,3 +1,22 @@ package com.DesktopApplicationClientJava.services; -public class UsersService {} +import com.DesktopApplicationClientJava.entities.User; +import com.DesktopApplicationClientJava.services.data.RegisterData; +import com.DesktopApplicationClientJava.services.data.UpdateUserData; +import com.DesktopApplicationClientJava.services.filter.UserFilter; +import java.util.List; +import java.util.UUID; + +public interface UsersService { + + /** Поиск с фильтрами. Пустые параметры = без фильтра. */ + List search(UserFilter filter, long offset, long count); + + User getById(UUID uuid); + + User create(RegisterData data); + + User update(UUID uuid, UpdateUserData data); + + void delete(UUID uuid); +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/data/RegisterData.java b/src/main/java/com/DesktopApplicationClientJava/services/data/RegisterData.java new file mode 100644 index 0000000..d4dea02 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/data/RegisterData.java @@ -0,0 +1,15 @@ +package com.DesktopApplicationClientJava.services.data; + +import com.DesktopApplicationClientJava.entities.Role; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class RegisterData { + private final String email; + private final String password; + private final Role role; + private final String firstName; + private final String lastName; +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/data/UpdateUserData.java b/src/main/java/com/DesktopApplicationClientJava/services/data/UpdateUserData.java new file mode 100644 index 0000000..32ad4ee --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/data/UpdateUserData.java @@ -0,0 +1,15 @@ +package com.DesktopApplicationClientJava.services.data; + +import com.DesktopApplicationClientJava.entities.Role; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UpdateUserData { + private final String email; + private final Role role; + private final String firstName; + private final String lastName; + private final Boolean enabled; +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/filter/ResourceFilter.java b/src/main/java/com/DesktopApplicationClientJava/services/filter/ResourceFilter.java new file mode 100644 index 0000000..2934918 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/filter/ResourceFilter.java @@ -0,0 +1,15 @@ +package com.DesktopApplicationClientJava.services.filter; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ResourceFilter { + private final String title; + private final String description; + + public static ResourceFilter empty() { + return new ResourceFilter(null, null); + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/filter/UserFilter.java b/src/main/java/com/DesktopApplicationClientJava/services/filter/UserFilter.java new file mode 100644 index 0000000..877cf7e --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/filter/UserFilter.java @@ -0,0 +1,19 @@ +package com.DesktopApplicationClientJava.services.filter; + +import com.DesktopApplicationClientJava.entities.Role; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UserFilter { + private final String email; + private final Role role; + private final String firstName; + private final String lastName; + private final Boolean enabled; + + public static UserFilter empty() { + return new UserFilter(null, null, null, null, null); + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java new file mode 100644 index 0000000..382de28 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java @@ -0,0 +1,75 @@ +package com.DesktopApplicationClientJava.services.impl; + +import com.DesktopApplicationClientJava.Api.ApiClient; +import com.DesktopApplicationClientJava.Api.ApiException; +import com.DesktopApplicationClientJava.Api.Dto.request.Auth.LoginRequest; +import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; +import com.DesktopApplicationClientJava.entities.Role; +import com.DesktopApplicationClientJava.entities.User; +import com.DesktopApplicationClientJava.services.AuthService; +import com.DesktopApplicationClientJava.services.ServiceException; +import com.DesktopApplicationClientJava.session.Session; +import java.io.IOException; + +public class AuthServiceImpl implements AuthService { + + private final ApiClient apiClient; + private final Session session; + + public AuthServiceImpl(ApiClient apiClient, Session session) { + this.apiClient = apiClient; + this.session = session; + } + + @Override + public User login(String email, String password) { + try { + apiClient.login(new LoginRequest(email, password)); + UserDto me = apiClient.getMe(); + User user = toEntity(me); + session.setCurrentUser(user); + return user; + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public void logout() { + try { + apiClient.logout(); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } finally { + session.clear(); + } + } + + @Override + public User getCurrentUser() { + return session.getCurrentUser(); + } + + @Override + public boolean hasRole(Role required) { + User current = session.getCurrentUser(); + return current != null && current.getRole() == required; + } + + @Override + public boolean isAdmin() { + return hasRole(Role.ADMIN); + } + + @Override + public boolean isModerator() { + return hasRole(Role.MODER); + } + + private User toEntity(UserDto dto) { + return new User( + dto.uuid(), dto.email(), dto.role(), dto.firstName(), dto.lastName(), dto.enabled()); + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/ExportServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/ExportServiceImpl.java new file mode 100644 index 0000000..577c5d4 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/ExportServiceImpl.java @@ -0,0 +1,54 @@ +package com.DesktopApplicationClientJava.services.impl; + +import com.DesktopApplicationClientJava.Api.ApiClient; +import com.DesktopApplicationClientJava.Api.ApiException; +import com.DesktopApplicationClientJava.services.ExportService; +import com.DesktopApplicationClientJava.services.ServiceException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class ExportServiceImpl implements ExportService { + + private final ApiClient apiClient; + + public ExportServiceImpl(ApiClient apiClient) { + this.apiClient = apiClient; + } + + @Override + public Path exportUsers(Path destinationDir) { + return saveTo(destinationDir, "users.xlsx", apiClient::exportUsers); + } + + @Override + public Path exportResources(Path destinationDir) { + return saveTo(destinationDir, "resources.xlsx", apiClient::exportResources); + } + + @Override + public Path exportResourceFiles(UUID resourceUuid, Path destinationDir) { + String filename = "files-" + resourceUuid + ".xlsx"; + return saveTo(destinationDir, filename, () -> apiClient.exportResourceFiles(resourceUuid)); + } + + @FunctionalInterface + private interface ByteSupplier { + byte[] get() throws IOException, InterruptedException; + } + + private Path saveTo(Path destinationDir, String filename, ByteSupplier supplier) { + try { + byte[] data = supplier.get(); + Files.createDirectories(destinationDir); + Path target = destinationDir.resolve(filename); + Files.write(target, data); + return target; + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка экспорта: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java new file mode 100644 index 0000000..26d037b --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java @@ -0,0 +1,51 @@ +package com.DesktopApplicationClientJava.services.impl; + +import com.DesktopApplicationClientJava.Api.ApiClient; +import com.DesktopApplicationClientJava.Api.ApiException; +import com.DesktopApplicationClientJava.Api.Dto.response.FileDto; +import com.DesktopApplicationClientJava.entities.FileInfo; +import com.DesktopApplicationClientJava.services.FileService; +import com.DesktopApplicationClientJava.services.ServiceException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.UUID; + +public class FileServiceImpl implements FileService { + + private final ApiClient apiClient; + + public FileServiceImpl(ApiClient apiClient) { + this.apiClient = apiClient; + } + + public FileInfo uploadFile(UUID resourceId, Path file) { + try { + FileDto dto = apiClient.uploadFile(resourceId, file); + return new FileInfo(dto.uuid(), dto.name(), dto.contentType(), dto.size()); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + public Path downloadFile(UUID resourceId, UUID fileId, Path destinationDir) { + try { + return apiClient.downloadFile(resourceId, fileId, destinationDir); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + public void deleteFile(UUID resourceId, UUID fileId) { + try { + apiClient.deleteFile(resourceId, fileId); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java new file mode 100644 index 0000000..25d3725 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java @@ -0,0 +1,89 @@ +package com.DesktopApplicationClientJava.services.impl; + +import com.DesktopApplicationClientJava.Api.ApiClient; +import com.DesktopApplicationClientJava.Api.ApiException; +import com.DesktopApplicationClientJava.Api.Dto.request.Resource.CreateResourceRequest; +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.Resource; +import com.DesktopApplicationClientJava.services.ResourceService; +import com.DesktopApplicationClientJava.services.ServiceException; +import com.DesktopApplicationClientJava.services.filter.ResourceFilter; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +public class ResourceServiceImpl implements ResourceService { + + private final ApiClient apiClient; + + public ResourceServiceImpl(ApiClient apiClient) { + this.apiClient = apiClient; + } + + @Override + public List search(ResourceFilter filter, long offset, long count) { + ResourceFilterRequest req = + new ResourceFilterRequest(filter.getTitle(), filter.getDescription(), null, null); + try { + return apiClient.findResourcesByFilters(req, offset, count, "createdAt", "desc").stream() + .map(this::toEntity) + .toList(); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public Resource create(String title, String description) { + try { + return toEntity(apiClient.createResource(new CreateResourceRequest(title, description))); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public Resource getById(UUID resourceId) { + try { + return toEntity(apiClient.findResourceById(resourceId)); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public Resource update(UUID resourceId, String title, String description) { + UpdateResourceRequest req = new UpdateResourceRequest(title, description); + try { + return toEntity(apiClient.updateResource(resourceId, req)); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public void delete(UUID resourceId) { + try { + apiClient.deleteResource(resourceId); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + private Resource toEntity(ResourceDto dto) { + return new Resource( + dto.uuid(), dto.title(), dto.description(), dto.createdAt(), dto.updatedAt()); + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java new file mode 100644 index 0000000..c4f1e7c --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java @@ -0,0 +1,112 @@ +package com.DesktopApplicationClientJava.services.impl; + +import com.DesktopApplicationClientJava.Api.ApiClient; +import com.DesktopApplicationClientJava.Api.ApiException; +import com.DesktopApplicationClientJava.Api.Dto.request.Admin.RegisterRequest; +import com.DesktopApplicationClientJava.Api.Dto.request.Admin.UpdateUserRequest; +import com.DesktopApplicationClientJava.Api.Dto.request.Admin.UserFilterRequest; +import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; +import com.DesktopApplicationClientJava.entities.User; +import com.DesktopApplicationClientJava.services.ServiceException; +import com.DesktopApplicationClientJava.services.UsersService; +import com.DesktopApplicationClientJava.services.data.RegisterData; +import com.DesktopApplicationClientJava.services.data.UpdateUserData; +import com.DesktopApplicationClientJava.services.filter.UserFilter; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +public class UsersServiceImpl implements UsersService { + private final ApiClient apiClient; + + public UsersServiceImpl(ApiClient apiClient) { + this.apiClient = apiClient; + } + + @Override + public List search(UserFilter filter, long offset, long count) { + UserFilterRequest req = + new UserFilterRequest( + filter.getEmail(), + filter.getRole(), + filter.getFirstName(), + filter.getLastName(), + filter.getEnabled(), + null, + null); + + try { + return apiClient.getUsersByFilters(req, offset, count, "createdAt", "desc").stream() + .map(this::toEntity) + .toList(); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public User getById(UUID uuid) { + try { + return toEntity(apiClient.getUserById(uuid)); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public User create(RegisterData data) { + RegisterRequest req = + new RegisterRequest( + data.getEmail(), + data.getPassword(), + data.getRole(), + data.getFirstName(), + data.getLastName()); + try { + return toEntity(apiClient.createUser(req)); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public User update(UUID uuid, UpdateUserData data) { + UpdateUserRequest req = + new UpdateUserRequest( + data.getEmail(), + data.getEnabled(), + data.getRole(), + data.getFirstName(), + data.getLastName()); + + try { + return toEntity(apiClient.updateUser(uuid, req)); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + @Override + public void delete(UUID uuid) { + try { + apiClient.deleteUser(uuid); + } catch (ApiException e) { + throw new ServiceException(e.getMessage(), e.getStatus()); + } catch (IOException | InterruptedException e) { + throw new ServiceException("Ошибка соединения с сервером"); + } + } + + private User toEntity(UserDto dto) { + return new User( + dto.uuid(), dto.email(), dto.role(), dto.firstName(), dto.lastName(), dto.enabled()); + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/session/Session.java b/src/main/java/com/DesktopApplicationClientJava/session/Session.java index 352b636..cea4c73 100644 --- a/src/main/java/com/DesktopApplicationClientJava/session/Session.java +++ b/src/main/java/com/DesktopApplicationClientJava/session/Session.java @@ -5,7 +5,12 @@ @NoArgsConstructor public class Session { + private User currentUser; + private String accessToken; + private String refreshToken; + + // ===== user ===== public User getCurrentUser() { return currentUser; @@ -15,7 +20,37 @@ public void setCurrentUser(User user) { this.currentUser = user; } + // ===== tokens ===== + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + // ===== helpers ===== + public boolean isLoggedIn() { - return currentUser != null; + return currentUser != null && accessToken != null; + } + + public void clearTokens() { + this.accessToken = null; + this.refreshToken = null; + } + + public void clear() { + this.currentUser = null; + clearTokens(); } } diff --git a/src/main/java/com/DesktopApplicationClientJava/utils/ControllerWiring.java b/src/main/java/com/DesktopApplicationClientJava/utils/ControllerWiring.java index f986f72..0589c50 100644 --- a/src/main/java/com/DesktopApplicationClientJava/utils/ControllerWiring.java +++ b/src/main/java/com/DesktopApplicationClientJava/utils/ControllerWiring.java @@ -2,15 +2,16 @@ import com.DesktopApplicationClientJava.navigation.Navigator; import com.DesktopApplicationClientJava.navigation.NavigatorAware; +import com.DesktopApplicationClientJava.services.ServiceRegistry; +import com.DesktopApplicationClientJava.services.ServicesAware; import com.DesktopApplicationClientJava.session.Session; import com.DesktopApplicationClientJava.session.SessionAware; -import javafx.stage.Stage; public final class ControllerWiring { - private ControllerWiring() {} - - public static void wire(Object controller, Stage stage, Session session) { + public static void wire( + Object controller, Navigator navigator, Session session, ServiceRegistry services) { if (controller instanceof SessionAware sa) sa.setSession(session); - if (controller instanceof NavigatorAware na) na.setNavigator(new Navigator(stage, session)); + if (controller instanceof NavigatorAware na) na.setNavigator(navigator); + if (controller instanceof ServicesAware sv) sv.setServices(services); } } diff --git a/src/main/resources/api.properties b/src/main/resources/api.properties new file mode 100644 index 0000000..bf9f27e --- /dev/null +++ b/src/main/resources/api.properties @@ -0,0 +1,16 @@ +api.baseUrl=http://localhost:8080 + +api.connectTimeoutMs=15000 +api.readTimeoutMs=30000 + +api.path.login=/api/v1/auth/login +api.path.refresh=/api/v1/auth/refresh +api.path.logout=/api/v1/auth/logout + +api.path.me=/api/v1/user/me + +api.path.resource=/api/v1/resource +api.path.admin=/api/v1/admin + +api.path.export.users=/api/v1/admin/users/export +api.path.export.resources=/api/v1/admin/resource/export \ No newline at end of file