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