From d91a3c9221c370df53a5947735f7fd2572ef4106 Mon Sep 17 00:00:00 2001 From: Vladimir Valekzhanin Date: Tue, 13 Oct 2026 19:31:44 +0300 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=D1=8F=D0=B2=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BC=D0=B0=D0=BF=D0=BF=D0=B5=D1=80=D1=8B=20dto=20=D0=B2=20ent?= =?UTF-8?q?ity=20=D0=B8=20=D1=81=D1=82=D1=80=D0=BE=D0=B3=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D1=81=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #19 --- .../Api/ApiClient.java | 4 -- .../Api/Dto/response/UserDto.java | 4 +- .../mapper/FileMapper.java | 13 +++++ .../mapper/ResourceMapper.java | 18 +++++++ .../mapper/UserMapper.java | 14 +++++ .../services/impl/AuthServiceImpl.java | 10 +--- .../services/impl/FileServiceImpl.java | 5 +- .../services/impl/ResourceServiceImpl.java | 23 +++----- .../services/impl/UsersServiceImpl.java | 15 ++---- src/test/java/mapper/FileMapperTest.java | 25 +++++++++ src/test/java/mapper/ResourceMapperTest.java | 54 +++++++++++++++++++ src/test/java/mapper/UserDtoParsingTest.java | 44 +++++++++++++++ src/test/java/mapper/UserMapperTest.java | 38 +++++++++++++ 13 files changed, 223 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java create mode 100644 src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java create mode 100644 src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java create mode 100644 src/test/java/mapper/FileMapperTest.java create mode 100644 src/test/java/mapper/ResourceMapperTest.java create mode 100644 src/test/java/mapper/UserDtoParsingTest.java create mode 100644 src/test/java/mapper/UserMapperTest.java diff --git a/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java b/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java index a2c7692..83425e8 100644 --- a/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java +++ b/src/main/java/com/DesktopApplicationClientJava/Api/ApiClient.java @@ -14,7 +14,6 @@ import com.DesktopApplicationClientJava.Api.Dto.response.ResourceDto; import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; import com.DesktopApplicationClientJava.session.Session; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.io.ByteArrayOutputStream; @@ -50,9 +49,6 @@ public ApiClient(Session session) { .build(); this.objectMapper = new ObjectMapper(); this.objectMapper.registerModule(new JavaTimeModule()); - // FIXME: temporary — backend returns firstname/lastname instead of firstName/lastName. - // Remove once backend uses camelCase; then unknown properties should fail fast again. - this.objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); } // ======================== AUTH ======================== diff --git a/src/main/java/com/DesktopApplicationClientJava/Api/Dto/response/UserDto.java b/src/main/java/com/DesktopApplicationClientJava/Api/Dto/response/UserDto.java index ea2d3a9..2259f22 100644 --- a/src/main/java/com/DesktopApplicationClientJava/Api/Dto/response/UserDto.java +++ b/src/main/java/com/DesktopApplicationClientJava/Api/Dto/response/UserDto.java @@ -8,8 +8,8 @@ public record UserDto( UUID uuid, String email, Role role, - String firstName, - String lastName, + String firstname, + String lastname, Boolean enabled, LocalDateTime createdAt, LocalDateTime updatedAt) {} diff --git a/src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java b/src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java new file mode 100644 index 0000000..006a0ed --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java @@ -0,0 +1,13 @@ +package com.DesktopApplicationClientJava.mapper; + +import com.DesktopApplicationClientJava.Api.Dto.response.FileDto; +import com.DesktopApplicationClientJava.entities.FileInfo; + +public final class FileMapper { + + private FileMapper() {} + + public static FileInfo toEntity(FileDto dto) { + return new FileInfo(dto.uuid(), dto.name(), dto.contentType(), dto.size()); + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java b/src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java new file mode 100644 index 0000000..00ddd3a --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java @@ -0,0 +1,18 @@ +package com.DesktopApplicationClientJava.mapper; + +import com.DesktopApplicationClientJava.Api.Dto.response.ResourceDto; +import com.DesktopApplicationClientJava.entities.FileInfo; +import com.DesktopApplicationClientJava.entities.Resource; +import java.util.List; + +public final class ResourceMapper { + + private ResourceMapper() {} + + public static Resource toEntity(ResourceDto dto) { + List files = + dto.files() == null ? List.of() : dto.files().stream().map(FileMapper::toEntity).toList(); + return new Resource( + dto.uuid(), dto.title(), dto.description(), dto.createdAt(), dto.updatedAt(), files); + } +} diff --git a/src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java b/src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java new file mode 100644 index 0000000..6abc557 --- /dev/null +++ b/src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package com.DesktopApplicationClientJava.mapper; + +import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; +import com.DesktopApplicationClientJava.entities.User; + +public final class UserMapper { + + private UserMapper() {} + + public static 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/AuthServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java index 6538915..01b8397 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java @@ -3,9 +3,9 @@ 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.mapper.UserMapper; import com.DesktopApplicationClientJava.services.AuthService; import com.DesktopApplicationClientJava.services.ServiceException; import com.DesktopApplicationClientJava.session.Session; @@ -25,8 +25,7 @@ public AuthServiceImpl(ApiClient apiClient, Session session) { public User login(String email, String password) { try { apiClient.login(new LoginRequest(email, password)); - UserDto me = apiClient.getMe(); - User user = toEntity(me); + User user = UserMapper.toEntity(apiClient.getMe()); session.setCurrentUser(user); return user; } catch (ApiException e) { @@ -70,11 +69,6 @@ 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()); - } - private static int level(Role role) { return switch (role) { case USER -> 0; diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java index 26d037b..706de9f 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java @@ -2,8 +2,8 @@ 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.mapper.FileMapper; import com.DesktopApplicationClientJava.services.FileService; import com.DesktopApplicationClientJava.services.ServiceException; import java.io.IOException; @@ -20,8 +20,7 @@ public FileServiceImpl(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()); + return FileMapper.toEntity(apiClient.uploadFile(resourceId, file)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java index febaa69..7df0eb4 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java @@ -5,9 +5,8 @@ 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.FileInfo; import com.DesktopApplicationClientJava.entities.Resource; +import com.DesktopApplicationClientJava.mapper.ResourceMapper; import com.DesktopApplicationClientJava.services.ResourceService; import com.DesktopApplicationClientJava.services.ServiceException; import com.DesktopApplicationClientJava.services.filter.ResourceFilter; @@ -29,7 +28,7 @@ public List search(ResourceFilter filter, long offset, long count) { new ResourceFilterRequest(filter.getTitle(), filter.getDescription(), null, null); try { return apiClient.findResourcesByFilters(req, offset, count, "createdAt", "desc").stream() - .map(this::toEntity) + .map(ResourceMapper::toEntity) .toList(); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); @@ -41,7 +40,8 @@ public List search(ResourceFilter filter, long offset, long count) { @Override public Resource create(String title, String description) { try { - return toEntity(apiClient.createResource(new CreateResourceRequest(title, description))); + return ResourceMapper.toEntity( + apiClient.createResource(new CreateResourceRequest(title, description))); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -52,7 +52,7 @@ public Resource create(String title, String description) { @Override public Resource getById(UUID resourceId) { try { - return toEntity(apiClient.findResourceById(resourceId)); + return ResourceMapper.toEntity(apiClient.findResourceById(resourceId)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -80,7 +80,7 @@ public List search( public Resource update(UUID resourceId, String title, String description) { UpdateResourceRequest req = new UpdateResourceRequest(title, description); try { - return toEntity(apiClient.updateResource(resourceId, req)); + return ResourceMapper.toEntity(apiClient.updateResource(resourceId, req)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -98,15 +98,4 @@ public void delete(UUID resourceId) { throw new ServiceException("Ошибка соединения с сервером"); } } - - private Resource toEntity(ResourceDto dto) { - List files = - dto.files() == null - ? List.of() - : dto.files().stream() - .map(f -> new FileInfo(f.uuid(), f.name(), f.contentType(), f.size())) - .toList(); - return new Resource( - dto.uuid(), dto.title(), dto.description(), dto.createdAt(), dto.updatedAt(), files); - } } diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java index c4f1e7c..220636f 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java @@ -5,8 +5,8 @@ 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.mapper.UserMapper; import com.DesktopApplicationClientJava.services.ServiceException; import com.DesktopApplicationClientJava.services.UsersService; import com.DesktopApplicationClientJava.services.data.RegisterData; @@ -37,7 +37,7 @@ public List search(UserFilter filter, long offset, long count) { try { return apiClient.getUsersByFilters(req, offset, count, "createdAt", "desc").stream() - .map(this::toEntity) + .map(UserMapper::toEntity) .toList(); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); @@ -49,7 +49,7 @@ public List search(UserFilter filter, long offset, long count) { @Override public User getById(UUID uuid) { try { - return toEntity(apiClient.getUserById(uuid)); + return UserMapper.toEntity(apiClient.getUserById(uuid)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -67,7 +67,7 @@ public User create(RegisterData data) { data.getFirstName(), data.getLastName()); try { - return toEntity(apiClient.createUser(req)); + return UserMapper.toEntity(apiClient.createUser(req)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -86,7 +86,7 @@ public User update(UUID uuid, UpdateUserData data) { data.getLastName()); try { - return toEntity(apiClient.updateUser(uuid, req)); + return UserMapper.toEntity(apiClient.updateUser(uuid, req)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -104,9 +104,4 @@ public void delete(UUID uuid) { 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/test/java/mapper/FileMapperTest.java b/src/test/java/mapper/FileMapperTest.java new file mode 100644 index 0000000..d77018c --- /dev/null +++ b/src/test/java/mapper/FileMapperTest.java @@ -0,0 +1,25 @@ +package mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.DesktopApplicationClientJava.Api.Dto.response.FileDto; +import com.DesktopApplicationClientJava.entities.FileInfo; +import com.DesktopApplicationClientJava.mapper.FileMapper; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class FileMapperTest { + + @Test + void mapsAllFields() { + UUID uuid = UUID.randomUUID(); + FileDto dto = new FileDto(uuid, "отчёт.pdf", "application/pdf", 12345L); + + FileInfo file = FileMapper.toEntity(dto); + + assertEquals(uuid, file.getUuid()); + assertEquals("отчёт.pdf", file.getName()); + assertEquals("application/pdf", file.getContentType()); + assertEquals(12345L, file.getSize()); + } +} diff --git a/src/test/java/mapper/ResourceMapperTest.java b/src/test/java/mapper/ResourceMapperTest.java new file mode 100644 index 0000000..6046c79 --- /dev/null +++ b/src/test/java/mapper/ResourceMapperTest.java @@ -0,0 +1,54 @@ +package mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.DesktopApplicationClientJava.Api.Dto.response.FileDto; +import com.DesktopApplicationClientJava.Api.Dto.response.ResourceDto; +import com.DesktopApplicationClientJava.entities.Resource; +import com.DesktopApplicationClientJava.mapper.ResourceMapper; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class ResourceMapperTest { + + @Test + void mapsAllFieldsIncludingFiles() { + UUID uuid = UUID.randomUUID(); + UUID fileUuid = UUID.randomUUID(); + ResourceDto dto = + new ResourceDto( + uuid, + "Отчёт", + "Описание", + LocalDateTime.parse("2026-01-02T03:04:05"), + LocalDateTime.parse("2026-01-03T04:05:06"), + List.of(new FileDto(fileUuid, "отчёт.pdf", "application/pdf", 12345L))); + + Resource resource = ResourceMapper.toEntity(dto); + + assertEquals(uuid, resource.getUuid()); + assertEquals("Отчёт", resource.getTitle()); + assertEquals("Описание", resource.getDescription()); + assertEquals(LocalDateTime.parse("2026-01-02T03:04:05"), resource.getCreatedAt()); + assertEquals(LocalDateTime.parse("2026-01-03T04:05:06"), resource.getUpdatedAt()); + assertEquals(1, resource.getFiles().size()); + assertEquals(fileUuid, resource.getFiles().get(0).getUuid()); + assertEquals("отчёт.pdf", resource.getFiles().get(0).getName()); + assertEquals("application/pdf", resource.getFiles().get(0).getContentType()); + assertEquals(12345L, resource.getFiles().get(0).getSize()); + } + + @Test + void nullFilesBecomeEmptyList() { + ResourceDto dto = + new ResourceDto( + UUID.randomUUID(), "Отчёт", "Описание", LocalDateTime.now(), LocalDateTime.now(), null); + + Resource resource = ResourceMapper.toEntity(dto); + + assertTrue(resource.getFiles().isEmpty()); + } +} diff --git a/src/test/java/mapper/UserDtoParsingTest.java b/src/test/java/mapper/UserDtoParsingTest.java new file mode 100644 index 0000000..1de23bd --- /dev/null +++ b/src/test/java/mapper/UserDtoParsingTest.java @@ -0,0 +1,44 @@ +package mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.Test; + +class UserDtoParsingTest { + + private final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + + @Test + void parsesBackendContractOneToOne() throws Exception { + String json = + """ + { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "email": "user@example.com", + "role": "USER", + "firstname": "Иван", + "lastname": "Петров", + "enabled": true, + "createdAt": "2026-01-02T03:04:05", + "updatedAt": "2026-01-03T04:05:06" + } + """; + + UserDto dto = mapper.readValue(json, UserDto.class); + + assertEquals("Иван", dto.firstname()); + assertEquals("Петров", dto.lastname()); + assertEquals("user@example.com", dto.email()); + } + + @Test + void unknownPropertiesFailFast() { + String json = "{\"uuid\": null, \"unexpectedField\": 1}"; + + assertThrows(Exception.class, () -> mapper.readValue(json, UserDto.class)); + } +} diff --git a/src/test/java/mapper/UserMapperTest.java b/src/test/java/mapper/UserMapperTest.java new file mode 100644 index 0000000..0e4fce2 --- /dev/null +++ b/src/test/java/mapper/UserMapperTest.java @@ -0,0 +1,38 @@ +package mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; +import com.DesktopApplicationClientJava.entities.Role; +import com.DesktopApplicationClientJava.entities.User; +import com.DesktopApplicationClientJava.mapper.UserMapper; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class UserMapperTest { + + @Test + void mapsBackendLowercaseNamesToCamelCaseFields() { + UUID uuid = UUID.randomUUID(); + UserDto dto = + new UserDto( + uuid, + "user@example.com", + Role.USER, + "Иван", + "Петров", + true, + LocalDateTime.parse("2026-01-02T03:04:05"), + LocalDateTime.parse("2026-01-03T04:05:06")); + + User user = UserMapper.toEntity(dto); + + assertEquals(uuid, user.getUuid()); + assertEquals("user@example.com", user.getEmail()); + assertEquals(Role.USER, user.getRole()); + assertEquals("Иван", user.getFirstName()); + assertEquals("Петров", user.getLastName()); + assertEquals(true, user.getEnabled()); + } +} From f9461d87442525446e007f83b8328fa377361a8e Mon Sep 17 00:00:00 2001 From: Vladimir Valekzhanin Date: Tue, 13 Oct 2026 19:38:14 +0300 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B8=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20overload?= =?UTF-8?q?=20search=20=D0=BD=D0=B0=20ResourceMapper=20=D0=BF=D0=BE=D1=81?= =?UTF-8?q?=D0=BB=D0=B5=20=D1=80=D0=B5=D0=B1=D0=B5=D0=B9=D0=B7=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/impl/ResourceServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java index 7df0eb4..c38a5bb 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java @@ -67,7 +67,7 @@ public List search( new ResourceFilterRequest(filter.getTitle(), filter.getDescription(), null, null); try { return apiClient.findResourcesByFilters(req, offset, count, sortBy, sortDir).stream() - .map(this::toEntity) + .map(ResourceMapper::toEntity) .toList(); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); From 5ae1e4d07638f07cf76f2cdf96505e912406d342 Mon Sep 17 00:00:00 2001 From: Vladimir Valekzhanin Date: Tue, 13 Oct 2026 19:57:09 +0300 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B8=20=D0=BC=D0=B0=D0=BF=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D1=8B=20=D0=BD=D0=B0=20mapstruct=20=D0=BA=D0=B0=D0=BA=20=D0=B2?= =?UTF-8?q?=20=D0=B1=D1=8D=D0=BA=D0=B5=D0=BD=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 17 ++++++++++++++++ .../mapper/FileMapper.java | 11 +++++----- .../mapper/ResourceMapper.java | 20 +++++++++---------- .../mapper/UserMapper.java | 16 +++++++++------ .../services/impl/AuthServiceImpl.java | 2 +- .../services/impl/FileServiceImpl.java | 2 +- .../services/impl/ResourceServiceImpl.java | 10 +++++----- .../services/impl/UsersServiceImpl.java | 8 ++++---- src/test/java/mapper/FileMapperTest.java | 2 +- src/test/java/mapper/ResourceMapperTest.java | 4 ++-- src/test/java/mapper/UserMapperTest.java | 2 +- 11 files changed, 58 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index 221d9f7..d2c0902 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,8 @@ UTF-8 ${encoding} ${encoding} + 1.6.3 + 0.2.0 integration @@ -95,6 +97,11 @@ 1.18.48 provided + + org.mapstruct + mapstruct + ${mapstruct.version} + com.dlsc.formsfx formsfx-core @@ -165,6 +172,16 @@ lombok 1.18.48 + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + -proc:full diff --git a/src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java b/src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java index 006a0ed..aefd1ce 100644 --- a/src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java +++ b/src/main/java/com/DesktopApplicationClientJava/mapper/FileMapper.java @@ -2,12 +2,13 @@ import com.DesktopApplicationClientJava.Api.Dto.response.FileDto; import com.DesktopApplicationClientJava.entities.FileInfo; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; -public final class FileMapper { +@Mapper +public interface FileMapper { - private FileMapper() {} + FileMapper INSTANCE = Mappers.getMapper(FileMapper.class); - public static FileInfo toEntity(FileDto dto) { - return new FileInfo(dto.uuid(), dto.name(), dto.contentType(), dto.size()); - } + FileInfo toEntity(FileDto dto); } diff --git a/src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java b/src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java index 00ddd3a..60628ec 100644 --- a/src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java +++ b/src/main/java/com/DesktopApplicationClientJava/mapper/ResourceMapper.java @@ -1,18 +1,18 @@ package com.DesktopApplicationClientJava.mapper; import com.DesktopApplicationClientJava.Api.Dto.response.ResourceDto; -import com.DesktopApplicationClientJava.entities.FileInfo; import com.DesktopApplicationClientJava.entities.Resource; -import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.factory.Mappers; -public final class ResourceMapper { +@Mapper( + uses = FileMapper.class, + // Бэкенд присылает null вместо пустого списка файлов. + nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) +public interface ResourceMapper { - private ResourceMapper() {} + ResourceMapper INSTANCE = Mappers.getMapper(ResourceMapper.class); - public static Resource toEntity(ResourceDto dto) { - List files = - dto.files() == null ? List.of() : dto.files().stream().map(FileMapper::toEntity).toList(); - return new Resource( - dto.uuid(), dto.title(), dto.description(), dto.createdAt(), dto.updatedAt(), files); - } + Resource toEntity(ResourceDto dto); } diff --git a/src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java b/src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java index 6abc557..fe8cd5e 100644 --- a/src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java +++ b/src/main/java/com/DesktopApplicationClientJava/mapper/UserMapper.java @@ -2,13 +2,17 @@ import com.DesktopApplicationClientJava.Api.Dto.response.UserDto; import com.DesktopApplicationClientJava.entities.User; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; -public final class UserMapper { +@Mapper +public interface UserMapper { - private UserMapper() {} + UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); - public static User toEntity(UserDto dto) { - return new User( - dto.uuid(), dto.email(), dto.role(), dto.firstname(), dto.lastname(), dto.enabled()); - } + // Имена в контракте бэкенда в нижнем регистре, в сущности — camelCase. + @Mapping(target = "firstName", source = "firstname") + @Mapping(target = "lastName", source = "lastname") + User toEntity(UserDto dto); } diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java index 01b8397..e6e0e24 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/AuthServiceImpl.java @@ -25,7 +25,7 @@ public AuthServiceImpl(ApiClient apiClient, Session session) { public User login(String email, String password) { try { apiClient.login(new LoginRequest(email, password)); - User user = UserMapper.toEntity(apiClient.getMe()); + User user = UserMapper.INSTANCE.toEntity(apiClient.getMe()); session.setCurrentUser(user); return user; } catch (ApiException e) { diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java index 706de9f..ef630a8 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/FileServiceImpl.java @@ -20,7 +20,7 @@ public FileServiceImpl(ApiClient apiClient) { public FileInfo uploadFile(UUID resourceId, Path file) { try { - return FileMapper.toEntity(apiClient.uploadFile(resourceId, file)); + return FileMapper.INSTANCE.toEntity(apiClient.uploadFile(resourceId, file)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java index c38a5bb..9b566a7 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/ResourceServiceImpl.java @@ -28,7 +28,7 @@ public List search(ResourceFilter filter, long offset, long count) { new ResourceFilterRequest(filter.getTitle(), filter.getDescription(), null, null); try { return apiClient.findResourcesByFilters(req, offset, count, "createdAt", "desc").stream() - .map(ResourceMapper::toEntity) + .map(ResourceMapper.INSTANCE::toEntity) .toList(); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); @@ -40,7 +40,7 @@ public List search(ResourceFilter filter, long offset, long count) { @Override public Resource create(String title, String description) { try { - return ResourceMapper.toEntity( + return ResourceMapper.INSTANCE.toEntity( apiClient.createResource(new CreateResourceRequest(title, description))); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); @@ -52,7 +52,7 @@ public Resource create(String title, String description) { @Override public Resource getById(UUID resourceId) { try { - return ResourceMapper.toEntity(apiClient.findResourceById(resourceId)); + return ResourceMapper.INSTANCE.toEntity(apiClient.findResourceById(resourceId)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -67,7 +67,7 @@ public List search( new ResourceFilterRequest(filter.getTitle(), filter.getDescription(), null, null); try { return apiClient.findResourcesByFilters(req, offset, count, sortBy, sortDir).stream() - .map(ResourceMapper::toEntity) + .map(ResourceMapper.INSTANCE::toEntity) .toList(); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); @@ -80,7 +80,7 @@ public List search( public Resource update(UUID resourceId, String title, String description) { UpdateResourceRequest req = new UpdateResourceRequest(title, description); try { - return ResourceMapper.toEntity(apiClient.updateResource(resourceId, req)); + return ResourceMapper.INSTANCE.toEntity(apiClient.updateResource(resourceId, req)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { diff --git a/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java b/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java index 220636f..38c44cf 100644 --- a/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java +++ b/src/main/java/com/DesktopApplicationClientJava/services/impl/UsersServiceImpl.java @@ -37,7 +37,7 @@ public List search(UserFilter filter, long offset, long count) { try { return apiClient.getUsersByFilters(req, offset, count, "createdAt", "desc").stream() - .map(UserMapper::toEntity) + .map(UserMapper.INSTANCE::toEntity) .toList(); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); @@ -49,7 +49,7 @@ public List search(UserFilter filter, long offset, long count) { @Override public User getById(UUID uuid) { try { - return UserMapper.toEntity(apiClient.getUserById(uuid)); + return UserMapper.INSTANCE.toEntity(apiClient.getUserById(uuid)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -67,7 +67,7 @@ public User create(RegisterData data) { data.getFirstName(), data.getLastName()); try { - return UserMapper.toEntity(apiClient.createUser(req)); + return UserMapper.INSTANCE.toEntity(apiClient.createUser(req)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { @@ -86,7 +86,7 @@ public User update(UUID uuid, UpdateUserData data) { data.getLastName()); try { - return UserMapper.toEntity(apiClient.updateUser(uuid, req)); + return UserMapper.INSTANCE.toEntity(apiClient.updateUser(uuid, req)); } catch (ApiException e) { throw new ServiceException(e.getMessage(), e.getStatus()); } catch (IOException | InterruptedException e) { diff --git a/src/test/java/mapper/FileMapperTest.java b/src/test/java/mapper/FileMapperTest.java index d77018c..d29e3cb 100644 --- a/src/test/java/mapper/FileMapperTest.java +++ b/src/test/java/mapper/FileMapperTest.java @@ -15,7 +15,7 @@ void mapsAllFields() { UUID uuid = UUID.randomUUID(); FileDto dto = new FileDto(uuid, "отчёт.pdf", "application/pdf", 12345L); - FileInfo file = FileMapper.toEntity(dto); + FileInfo file = FileMapper.INSTANCE.toEntity(dto); assertEquals(uuid, file.getUuid()); assertEquals("отчёт.pdf", file.getName()); diff --git a/src/test/java/mapper/ResourceMapperTest.java b/src/test/java/mapper/ResourceMapperTest.java index 6046c79..9fafa94 100644 --- a/src/test/java/mapper/ResourceMapperTest.java +++ b/src/test/java/mapper/ResourceMapperTest.java @@ -27,7 +27,7 @@ void mapsAllFieldsIncludingFiles() { LocalDateTime.parse("2026-01-03T04:05:06"), List.of(new FileDto(fileUuid, "отчёт.pdf", "application/pdf", 12345L))); - Resource resource = ResourceMapper.toEntity(dto); + Resource resource = ResourceMapper.INSTANCE.toEntity(dto); assertEquals(uuid, resource.getUuid()); assertEquals("Отчёт", resource.getTitle()); @@ -47,7 +47,7 @@ void nullFilesBecomeEmptyList() { new ResourceDto( UUID.randomUUID(), "Отчёт", "Описание", LocalDateTime.now(), LocalDateTime.now(), null); - Resource resource = ResourceMapper.toEntity(dto); + Resource resource = ResourceMapper.INSTANCE.toEntity(dto); assertTrue(resource.getFiles().isEmpty()); } diff --git a/src/test/java/mapper/UserMapperTest.java b/src/test/java/mapper/UserMapperTest.java index 0e4fce2..66486b8 100644 --- a/src/test/java/mapper/UserMapperTest.java +++ b/src/test/java/mapper/UserMapperTest.java @@ -26,7 +26,7 @@ void mapsBackendLowercaseNamesToCamelCaseFields() { LocalDateTime.parse("2026-01-02T03:04:05"), LocalDateTime.parse("2026-01-03T04:05:06")); - User user = UserMapper.toEntity(dto); + User user = UserMapper.INSTANCE.toEntity(dto); assertEquals(uuid, user.getUuid()); assertEquals("user@example.com", user.getEmail());