From 4f7f1ad0d22161c2c55f774531ec5231ddc290dc Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 17:31:44 +0300 Subject: [PATCH 01/15] feat: added exception handling --- pom.xml | 2 + .../shareit/exception/ErrorResponse.java | 4 ++ .../exception/GlobalExceptionHandler.java | 61 +++++++++++++++++++ .../shareit/exception/NotFoundException.java | 13 ++++ .../exception/ValidationErrorResponse.java | 6 ++ .../shareit/exception/Violation.java | 4 ++ 6 files changed, 90 insertions(+) create mode 100644 src/main/java/ru/practicum/shareit/exception/ErrorResponse.java create mode 100644 src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/ru/practicum/shareit/exception/NotFoundException.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ValidationErrorResponse.java create mode 100644 src/main/java/ru/practicum/shareit/exception/Violation.java diff --git a/pom.xml b/pom.xml index 2db888c..ac238ab 100644 --- a/pom.xml +++ b/pom.xml @@ -51,11 +51,13 @@ h2 test + org.springframework.boot spring-boot-starter-test test + org.springframework.boot spring-boot-starter-validation diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java new file mode 100644 index 0000000..f45d074 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java @@ -0,0 +1,4 @@ +package ru.practicum.shareit.exception; + +public record ErrorResponse(String name, String message) { +} diff --git a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java b/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..47a0213 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.exception; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@ControllerAdvice +class GlobalExceptionHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleException(Exception ex) { + log.error("Error occurred while processing request {}", ex.getMessage()); + return new ErrorResponse("internal server error", + "An error occurred while processing request"); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ValidationErrorResponse onConstraintValidationException( + ConstraintViolationException e + ) { + final List violations = e.getConstraintViolations().stream() + .map( + violation -> new Violation( + violation.getPropertyPath().toString(), + violation.getMessage() + ) + ) + .collect(Collectors.toList()); + log.warn(violations.toString()); + return new ValidationErrorResponse(violations); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ValidationErrorResponse onMethodArgumentNotValidException( + MethodArgumentNotValidException e + ) { + final List violations = e.getBindingResult().getFieldErrors().stream() + .map(error -> new Violation(error.getField(), error.getDefaultMessage())) + .collect(Collectors.toList()); + log.warn(violations.toString()); + return new ValidationErrorResponse(violations); + } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse onNotFoundException(NotFoundException ex) { + log.warn("Not found exception occurred while processing request {}", ex.getMessage()); + return new ErrorResponse("not found", ex.getMessage()); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/NotFoundException.java b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java new file mode 100644 index 0000000..9dcff00 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.exception; + +import java.util.function.Supplier; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } + + public static Supplier supplier(String message, Object... args) { + return () -> new NotFoundException(message.formatted(args)); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationErrorResponse.java b/src/main/java/ru/practicum/shareit/exception/ValidationErrorResponse.java new file mode 100644 index 0000000..71d0271 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ValidationErrorResponse.java @@ -0,0 +1,6 @@ +package ru.practicum.shareit.exception; + +import java.util.List; + +public record ValidationErrorResponse(List violations) { +} diff --git a/src/main/java/ru/practicum/shareit/exception/Violation.java b/src/main/java/ru/practicum/shareit/exception/Violation.java new file mode 100644 index 0000000..68c9f12 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/Violation.java @@ -0,0 +1,4 @@ +package ru.practicum.shareit.exception; + +public record Violation(String fieldName, String message) { +} From 8f698afb1aac3fb2b41b8e8d6452ea9dbd284131 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 18:39:02 +0300 Subject: [PATCH 02/15] feat: added duplicate data exception --- .../shareit/exception/DuplicateDataException.java | 7 +++++++ .../shareit/exception/GlobalExceptionHandler.java | 13 ++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java diff --git a/src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java b/src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java new file mode 100644 index 0000000..c453ae0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/DuplicateDataException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class DuplicateDataException extends RuntimeException { + public DuplicateDataException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java b/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java index 47a0213..8b1d774 100644 --- a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java +++ b/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java @@ -4,20 +4,20 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.List; import java.util.stream.Collectors; @Slf4j -@ControllerAdvice +@RestControllerAdvice class GlobalExceptionHandler { @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorResponse handleException(Exception ex) { + public ErrorResponse onException(Exception ex) { log.error("Error occurred while processing request {}", ex.getMessage()); return new ErrorResponse("internal server error", "An error occurred while processing request"); @@ -58,4 +58,11 @@ public ErrorResponse onNotFoundException(NotFoundException ex) { log.warn("Not found exception occurred while processing request {}", ex.getMessage()); return new ErrorResponse("not found", ex.getMessage()); } + + @ExceptionHandler(DuplicateDataException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse onDuplicateDataException(DuplicateDataException ex) { + log.warn("Duplicate data exception occurred while processing request {}", ex.getMessage()); + return new ErrorResponse("duplicate data", ex.getMessage()); + } } From d1a3d32624d419cada9a19317fec4d040f321464 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 18:40:48 +0300 Subject: [PATCH 03/15] feat: added user dto's and mapper for them --- .../java/ru/practicum/shareit/user/User.java | 7 ---- .../ru/practicum/shareit/user/UserMapper.java | 37 +++++++++++++++++++ .../shareit/user/dto/NewUserDto.java | 11 ++++++ .../shareit/user/dto/UpdateUserDto.java | 26 +++++++++++++ .../practicum/shareit/user/dto/UserDto.java | 4 ++ .../ru/practicum/shareit/user/model/User.java | 17 +++++++++ 6 files changed, 95 insertions(+), 7 deletions(-) delete mode 100644 src/main/java/ru/practicum/shareit/user/User.java create mode 100644 src/main/java/ru/practicum/shareit/user/UserMapper.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/NewUserDto.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/UserDto.java create mode 100644 src/main/java/ru/practicum/shareit/user/model/User.java diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/src/main/java/ru/practicum/shareit/user/User.java deleted file mode 100644 index ae6e7f3..0000000 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.user; - -/** - * TODO Sprint add-controllers. - */ -public class User { -} diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/src/main/java/ru/practicum/shareit/user/UserMapper.java new file mode 100644 index 0000000..f94c933 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -0,0 +1,37 @@ +package ru.practicum.shareit.user; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.user.dto.NewUserDto; +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +@UtilityClass +public class UserMapper { + + public UserDto toUserDto(User user) { + return new UserDto( + user.getId(), + user.getName(), + user.getEmail()); + } + + public User toUser(NewUserDto newUser) { + User user = new User(); + user.setName(newUser.name()); + user.setEmail(newUser.email()); + return user; + } + + public User updateUser(User user, UpdateUserDto updatedUser) { + if (updatedUser.hasName()) { + user.setName(updatedUser.getName()); + } + + if (updatedUser.hasEmail()) { + user.setEmail(updatedUser.getEmail()); + } + + return user; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/NewUserDto.java b/src/main/java/ru/practicum/shareit/user/dto/NewUserDto.java new file mode 100644 index 0000000..3351658 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/NewUserDto.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record NewUserDto( + @NotBlank String name, + @NotNull @Email String email +) { +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java new file mode 100644 index 0000000..30ab13d --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class UpdateUserDto { + Long id; + String name; + + @Email + String email; + + public boolean hasName() { + return name != null && !name.isEmpty(); + } + + public boolean hasEmail() { + return email != null; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..96feaeb --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,4 @@ +package ru.practicum.shareit.user.dto; + +public record UserDto(Long id, String name, String email) { +} diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java new file mode 100644 index 0000000..7cd8314 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.user.model; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@NoArgsConstructor +@AllArgsConstructor +public class User { + Long id; + String name; + String email; +} From 57b63e612226bd95ba209970eb1dca7636acc469 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 18:41:18 +0300 Subject: [PATCH 04/15] feat: added repository layer for user --- .../user/repository/FakeUserRepository.java | 74 +++++++++++++++++++ .../user/repository/UserRepository.java | 19 +++++ 2 files changed, 93 insertions(+) create mode 100644 src/main/java/ru/practicum/shareit/user/repository/FakeUserRepository.java create mode 100644 src/main/java/ru/practicum/shareit/user/repository/UserRepository.java diff --git a/src/main/java/ru/practicum/shareit/user/repository/FakeUserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/FakeUserRepository.java new file mode 100644 index 0000000..0c66c89 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/FakeUserRepository.java @@ -0,0 +1,74 @@ +package ru.practicum.shareit.user.repository; + +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.exception.DuplicateDataException; +import ru.practicum.shareit.user.model.User; + +import java.util.*; + +@Repository +public class FakeUserRepository implements UserRepository { + private final Map users = new HashMap<>(); + private final Set userEmails = new HashSet<>(); + + @Override + public Collection findAll() { + return Collections.unmodifiableCollection(users.values()); + } + + @Override + public Optional findById(long id) { + User user = users.get(id); + if (user == null) { + return Optional.empty(); + } + + return Optional.of(new User( + user.getId(), + user.getName(), + user.getEmail())); + } + + @Override + public User save(User user) { + Long id = generateNextId(); + user.setId(id); + + if (userEmails.contains(user.getEmail())) { + throw new DuplicateDataException("email %s already exists".formatted(user.getEmail())); + } + + users.put(id, user); + userEmails.add(user.getEmail()); + return user; + } + + @Override + public void update(User user) { + User currentUser = users.get(user.getId()); + + if (!currentUser.getEmail().equals(user.getEmail())) { + if (userEmails.contains(user.getEmail())) { + throw new DuplicateDataException("email %s already exists".formatted(user.getEmail())); + } + + userEmails.remove(currentUser.getEmail()); + userEmails.add(user.getEmail()); + } + + users.put(user.getId(), user); + } + + @Override + public void delete(long id) { + User removedUser = users.remove(id); + userEmails.remove(removedUser.getEmail()); + } + + private Long generateNextId() { + Long nextId = users.keySet().stream() + .max(Long::compareTo) + .orElse(0L); + return ++nextId; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java new file mode 100644 index 0000000..be812dc --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.repository; + +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.Optional; + +public interface UserRepository { + + Collection findAll(); + + Optional findById(long id); + + User save(User user); + + void update(User user); + + void delete(long id); +} From 6f177b53b5db2afe420e3967bc185c8ceb2eabc4 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 18:42:02 +0300 Subject: [PATCH 05/15] feat: updated user controller for all crud operations and added user service --- .../shareit/user/UserController.java | 55 +++++++++++++++-- .../practicum/shareit/user/UserService.java | 19 ++++++ .../shareit/user/UserServiceImpl.java | 60 +++++++++++++++++++ 3 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/user/UserService.java create mode 100644 src/main/java/ru/practicum/shareit/user/UserServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index 03039b9..d14c082 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,55 @@ package ru.practicum.shareit.user; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.NewUserDto; +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; -/** - * TODO Sprint add-controllers. - */ +import java.util.Collection; + +@Slf4j +@Validated @RestController -@RequestMapping(path = "/users") +@RequestMapping("/users") +@RequiredArgsConstructor public class UserController { + private final UserService userService; + + @GetMapping + public Collection getAllUsers() { + log.trace("get all users requested"); + return userService.findAll(); + } + + @GetMapping("/{userId}") + public UserDto getUser(@PathVariable long userId) { + log.trace("get user requested with id: {}", userId); + return userService.findById(userId); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public UserDto createUser(@RequestBody @Valid NewUserDto newUserDto) { + log.trace("create user requested with body: {}", newUserDto); + return userService.save(newUserDto); + } + + @PatchMapping("/{userId}") + public UserDto updateUser(@RequestBody @Valid UpdateUserDto updateUserDto, @PathVariable long userId) { + updateUserDto.setId(userId); + log.trace("update user requested with id: {} and body {}", userId, updateUserDto); + return userService.update(updateUserDto); + } + + @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable long userId) { + log.trace("delete user requested with id: {}", userId); + userService.delete(userId); + } } diff --git a/src/main/java/ru/practicum/shareit/user/UserService.java b/src/main/java/ru/practicum/shareit/user/UserService.java new file mode 100644 index 0000000..3bdf54a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserService.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user; + +import ru.practicum.shareit.user.dto.NewUserDto; +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.Collection; + +public interface UserService { + Collection findAll(); + + UserDto findById(long id); + + UserDto save(NewUserDto user); + + UserDto update(UpdateUserDto user); + + void delete(long id); +} diff --git a/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java new file mode 100644 index 0000000..bb48c34 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java @@ -0,0 +1,60 @@ +package ru.practicum.shareit.user; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.user.dto.NewUserDto; +import ru.practicum.shareit.user.dto.UpdateUserDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.Collection; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + @Override + public Collection findAll() { + return userRepository.findAll() + .stream() + .map(UserMapper::toUserDto) + .toList(); + } + + @Override + public UserDto findById(long id) { + return UserMapper.toUserDto(getUserOrThrow(id)); + } + + @Override + public UserDto save(NewUserDto newUser) { + User user = UserMapper.toUser(newUser); + user = userRepository.save(user); + return UserMapper.toUserDto(user); + } + + @Override + public UserDto update(UpdateUserDto newUser) { + User user = getUserOrThrow(newUser.getId()); + User updatedUser = UserMapper.updateUser(user, newUser); + userRepository.update(updatedUser); + return UserMapper.toUserDto(updatedUser); + } + + @Override + public void delete(long id) { + getUserOrThrow(id); + userRepository.delete(id); + } + + private User getUserOrThrow(long id) { + return userRepository.findById(id).orElseThrow( + NotFoundException.supplier("User with id %d not found", id) + ); + } +} From 12f961b4e20a80bbc6f3c20cdd319ad3d936f792 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 20:09:34 +0300 Subject: [PATCH 06/15] fix: changed name fakeuserrepo to inmemory --- .../{FakeUserRepository.java => InMemoryUserRepository.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/ru/practicum/shareit/user/repository/{FakeUserRepository.java => InMemoryUserRepository.java} (96%) diff --git a/src/main/java/ru/practicum/shareit/user/repository/FakeUserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java similarity index 96% rename from src/main/java/ru/practicum/shareit/user/repository/FakeUserRepository.java rename to src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java index 0c66c89..7d7bd18 100644 --- a/src/main/java/ru/practicum/shareit/user/repository/FakeUserRepository.java +++ b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java @@ -7,7 +7,7 @@ import java.util.*; @Repository -public class FakeUserRepository implements UserRepository { +public class InMemoryUserRepository implements UserRepository { private final Map users = new HashMap<>(); private final Set userEmails = new HashSet<>(); From b02bd81b45c65b2edf97e149e59ef9c6cb5ce0c6 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 20:36:28 +0300 Subject: [PATCH 07/15] fix: changed isEmpty check to isBlank --- src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java index 30ab13d..5bbd748 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java +++ b/src/main/java/ru/practicum/shareit/user/dto/UpdateUserDto.java @@ -17,7 +17,7 @@ public class UpdateUserDto { String email; public boolean hasName() { - return name != null && !name.isEmpty(); + return name != null && !name.isBlank(); } public boolean hasEmail() { From 99b44c8d89a6f2b1f7a020d53a7b11f083712938 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 21:21:10 +0300 Subject: [PATCH 08/15] feat: added ForbiddenAccessException and it handler --- .../exception/ForbiddenAccessException.java | 7 +++++++ .../shareit/exception/GlobalExceptionHandler.java | 15 +++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java b/src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java new file mode 100644 index 0000000..214dfa4 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ForbiddenAccessException extends RuntimeException { + public ForbiddenAccessException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java b/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java index 8b1d774..61a234f 100644 --- a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java +++ b/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java @@ -26,9 +26,9 @@ public ErrorResponse onException(Exception ex) { @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ValidationErrorResponse onConstraintValidationException( - ConstraintViolationException e + ConstraintViolationException ex ) { - final List violations = e.getConstraintViolations().stream() + final List violations = ex.getConstraintViolations().stream() .map( violation -> new Violation( violation.getPropertyPath().toString(), @@ -43,9 +43,9 @@ public ValidationErrorResponse onConstraintValidationException( @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ValidationErrorResponse onMethodArgumentNotValidException( - MethodArgumentNotValidException e + MethodArgumentNotValidException ex ) { - final List violations = e.getBindingResult().getFieldErrors().stream() + final List violations = ex.getBindingResult().getFieldErrors().stream() .map(error -> new Violation(error.getField(), error.getDefaultMessage())) .collect(Collectors.toList()); log.warn(violations.toString()); @@ -65,4 +65,11 @@ public ErrorResponse onDuplicateDataException(DuplicateDataException ex) { log.warn("Duplicate data exception occurred while processing request {}", ex.getMessage()); return new ErrorResponse("duplicate data", ex.getMessage()); } + + @ExceptionHandler(ForbiddenAccessException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorResponse onForbiddenAccessException(ForbiddenAccessException ex) { + log.warn("Forbidden access exception occurred while processing request {}", ex.getMessage()); + return new ErrorResponse("forbidden", ex.getMessage()); + } } From 217a560b9ff830e5a8d9afd651a236755cc4d891 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 21:43:51 +0300 Subject: [PATCH 09/15] feat: added item model, dto's and ItemMapper --- .../ru/practicum/shareit/item/ItemMapper.java | 45 +++++++++++++++++++ .../practicum/shareit/item/dto/ItemDto.java | 11 +++-- .../shareit/item/dto/NewItemDto.java | 16 +++++++ .../shareit/item/dto/UpdateItemDto.java | 19 ++++++++ .../ru/practicum/shareit/item/model/Item.java | 22 +++++++-- .../shareit/request/ItemRequest.java | 7 --- .../shareit/request/model/ItemRequest.java | 14 ++++++ 7 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/item/ItemMapper.java create mode 100644 src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java create mode 100644 src/main/java/ru/practicum/shareit/item/dto/UpdateItemDto.java delete mode 100644 src/main/java/ru/practicum/shareit/request/ItemRequest.java create mode 100644 src/main/java/ru/practicum/shareit/request/model/ItemRequest.java diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/ItemMapper.java new file mode 100644 index 0000000..93d3883 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit.item; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.NewItemDto; +import ru.practicum.shareit.item.dto.UpdateItemDto; +import ru.practicum.shareit.item.model.Item; + +@UtilityClass +public class ItemMapper { + + public ItemDto toItemDto(Item item) { + return new ItemDto( + item.getId(), + item.getName(), + item.getDescription(), + item.isAvailable(), + item.getRequest() != null ? item.getRequest().getId() : null + ); + } + + public Item toItem(NewItemDto newItemDto) { + Item item = new Item(); + item.setName(newItemDto.name()); + item.setDescription(newItemDto.description()); + item.setAvailable(newItemDto.available()); + return item; + } + + public Item updateItem(Item item, UpdateItemDto updateItemDto) { + if (updateItemDto.hasName()) { + item.setName(updateItemDto.name()); + } + + if (updateItemDto.hasDescription()) { + item.setDescription(updateItemDto.description()); + } + + if (updateItemDto.hasAvailable()) { + item.setAvailable(updateItemDto.available()); + } + + return item; + } +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 9319d7d..30863dd 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -1,7 +1,10 @@ package ru.practicum.shareit.item.dto; -/** - * TODO Sprint add-controllers. - */ -public class ItemDto { +public record ItemDto( + Long id, + String name, + String description, + boolean available, + Long requestId +) { } diff --git a/src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java new file mode 100644 index 0000000..8e4ab57 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record NewItemDto( + @NotBlank + String name, + + @NotBlank + String description, + + @NotNull + Boolean available +) { +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/UpdateItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/UpdateItemDto.java new file mode 100644 index 0000000..8332e19 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/UpdateItemDto.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.item.dto; + +public record UpdateItemDto( + String name, + String description, + Boolean available +) { + public boolean hasName() { + return name != null && !name.isBlank(); + } + + public boolean hasDescription() { + return description != null && !description.isBlank(); + } + + public boolean hasAvailable() { + return available != null; + } +} diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/src/main/java/ru/practicum/shareit/item/model/Item.java index 44eb73d..775a4cd 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,7 +1,23 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@NoArgsConstructor +@AllArgsConstructor public class Item { + Long id; + String name; + String description; + boolean available; + User owner; + ItemRequest request; + } diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java deleted file mode 100644 index 95d6f23..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequest { -} diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java new file mode 100644 index 0000000..6f4ab59 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.request.model; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +/** + * TODO Sprint add-item-requests. + */ +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ItemRequest { + Long id; +} From da84bc012a45bd9a4219f7cf7938058b4d380964 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 21:44:43 +0300 Subject: [PATCH 10/15] feat: added inmemory item repository --- .../repository/InMemoryItemRepository.java | 69 +++++++++++++++++++ .../item/repository/ItemRepository.java | 16 +++++ 2 files changed, 85 insertions(+) create mode 100644 src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java create mode 100644 src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java new file mode 100644 index 0000000..d316585 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java @@ -0,0 +1,69 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.model.Item; + +import java.util.*; + +@Repository +public class InMemoryItemRepository implements ItemRepository { + private final Map items = new HashMap<>(); + + @Override + public Optional findById(long id) { + Item item = items.get(id); + if (item == null) { + return Optional.empty(); + } + + return Optional.of(new Item( + item.getId(), + item.getName(), + item.getDescription(), + item.isAvailable(), + item.getOwner(), + item.getRequest() + )); + } + + @Override + public Collection findAllByUserId(long userId) { + return items.values() + .stream() + .filter(item -> item.getOwner().getId() == userId) + .toList(); + } + + @Override + public Item save(Item item) { + Long id = generateNextId(); + item.setId(id); + + items.put(id, item); + + return item; + } + + @Override + public Collection searchItems(String query) { + String lowercaseQuery = query.trim().toLowerCase(); + + if (lowercaseQuery.isEmpty()) { + return Collections.emptyList(); + } + + return items.values() + .stream() + .filter(Item::isAvailable) + .filter(item -> item.getName().toLowerCase().contains(lowercaseQuery) + || item.getDescription().toLowerCase().contains(lowercaseQuery)) + .toList(); + } + + private Long generateNextId() { + Long nextId = items.keySet().stream() + .max(Long::compareTo) + .orElse(0L); + return ++nextId; + } +} diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java new file mode 100644 index 0000000..6426fb6 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.item.repository; + +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; +import java.util.Optional; + +public interface ItemRepository { + Optional findById(long id); + + Collection findAllByUserId(long userId); + + Item save(Item item); + + Collection searchItems(String query); +} From c4444b5345dd854bdf02b35318902a1a05c173f7 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 21:45:03 +0300 Subject: [PATCH 11/15] feat: updated controller and added service layer for item --- .../shareit/item/ItemController.java | 59 +++++++++++-- .../practicum/shareit/item/ItemService.java | 19 +++++ .../shareit/item/ItemServiceImpl.java | 85 +++++++++++++++++++ 3 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/item/ItemService.java create mode 100644 src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668..36ff3be 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,61 @@ package ru.practicum.shareit.item; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.NewItemDto; +import ru.practicum.shareit.item.dto.UpdateItemDto; -/** - * TODO Sprint add-controllers. - */ +import java.util.Collection; + +@Slf4j +@Validated @RestController @RequestMapping("/items") +@RequiredArgsConstructor public class ItemController { + private static final String SHARER_USER_ID_HEADER = "X-Sharer-User-Id"; + private final ItemService itemService; + + @GetMapping("/{itemId}") + public ItemDto getItem( + @RequestHeader(SHARER_USER_ID_HEADER) long userId, + @PathVariable long itemId + ) { + return itemService.getItemOfUserById(userId, itemId); + } + + @GetMapping + public Collection getItems( + @RequestHeader(SHARER_USER_ID_HEADER) long userId + ) { + return itemService.getAllItemsOfUser(userId); + } + + @PostMapping + public ItemDto createItem( + @RequestHeader(SHARER_USER_ID_HEADER) long userId, + @RequestBody @Valid NewItemDto newItemDto + ) { + return itemService.saveItem(userId, newItemDto); + } + + @PatchMapping("/{itemId}") + public ItemDto updateItem( + @RequestHeader(SHARER_USER_ID_HEADER) long userId, + @PathVariable long itemId, + @RequestBody @Valid UpdateItemDto updatedItem + ) { + return itemService.updateItem(userId, itemId, updatedItem); + } + + @GetMapping("/search") + public Collection searchItems( + @RequestParam(name = "text") String query + ) { + return itemService.searchItems(query); + } } diff --git a/src/main/java/ru/practicum/shareit/item/ItemService.java b/src/main/java/ru/practicum/shareit/item/ItemService.java new file mode 100644 index 0000000..86db07f --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemService.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.item; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.NewItemDto; +import ru.practicum.shareit.item.dto.UpdateItemDto; + +import java.util.Collection; + +public interface ItemService { + ItemDto getItemOfUserById(long userId, long itemId); + + Collection getAllItemsOfUser(long userId); + + ItemDto saveItem(long userId, NewItemDto newItem); + + ItemDto updateItem(long userId, long itemId, UpdateItemDto updatedItem); + + Collection searchItems(String query); +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java new file mode 100644 index 0000000..13fb79a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java @@ -0,0 +1,85 @@ +package ru.practicum.shareit.item; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.ForbiddenAccessException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.NewItemDto; +import ru.practicum.shareit.item.dto.UpdateItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.Collection; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + private final ItemRepository itemRepository; + private final UserRepository userRepository; + + @Override + public ItemDto getItemOfUserById(long userId, long itemId) { + getUserOrThrow(userId); + + return itemRepository.findById(itemId) + .map(ItemMapper::toItemDto) + .orElseThrow(NotFoundException.supplier("item with id %d not found", itemId)); + } + + @Override + public Collection getAllItemsOfUser(long userId) { + getUserOrThrow(userId); + + return itemRepository.findAllByUserId(userId) + .stream() + .map(ItemMapper::toItemDto) + .toList(); + } + + @Override + public ItemDto saveItem(long userId, NewItemDto newItem) { + User owner = getUserOrThrow(userId); + Item item = ItemMapper.toItem(newItem); + item.setOwner(owner); + item = itemRepository.save(item); + return ItemMapper.toItemDto(item); + } + + @Override + public ItemDto updateItem(long userId, long itemId, UpdateItemDto newItem) { + getUserOrThrow(userId); + Item item = getItemOrThrow(itemId); + if (item.getOwner().getId() != userId) { + throw new ForbiddenAccessException("Only owner of item can update it"); + } + Item updatedItem = ItemMapper.updateItem(item, newItem); + itemRepository.save(updatedItem); + return ItemMapper.toItemDto(updatedItem); + } + + + @Override + public Collection searchItems(String query) { + return itemRepository.searchItems(query) + .stream() + .map(ItemMapper::toItemDto) + .toList(); + } + + private Item getItemOrThrow(long itemId) { + return itemRepository.findById(itemId).orElseThrow( + NotFoundException.supplier("Item with id %d not found", itemId) + ); + } + + private User getUserOrThrow(long userId) { + return userRepository.findById(userId).orElseThrow( + NotFoundException.supplier("User with id %d not found", userId) + ); + } +} From f742aeabd9ca0dee2ee19212fd3e81d3bffdb3eb Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sat, 25 Oct 2025 21:49:18 +0300 Subject: [PATCH 12/15] fix: changed line brakes --- .../ru/practicum/shareit/item/dto/NewItemDto.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java index 8e4ab57..65648c3 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/NewItemDto.java @@ -4,13 +4,8 @@ import jakarta.validation.constraints.NotNull; public record NewItemDto( - @NotBlank - String name, - - @NotBlank - String description, - - @NotNull - Boolean available + @NotBlank String name, + @NotBlank String description, + @NotNull Boolean available ) { } From c86616d30ea1b8829555b59b4557ef9533b2b7b8 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sun, 26 Oct 2025 00:37:25 +0300 Subject: [PATCH 13/15] chore: method line breaks adjustment --- src/main/java/ru/practicum/shareit/user/UserController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index d14c082..8516884 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -40,7 +40,10 @@ public UserDto createUser(@RequestBody @Valid NewUserDto newUserDto) { } @PatchMapping("/{userId}") - public UserDto updateUser(@RequestBody @Valid UpdateUserDto updateUserDto, @PathVariable long userId) { + public UserDto updateUser( + @RequestBody @Valid UpdateUserDto updateUserDto, + @PathVariable long userId + ) { updateUserDto.setId(userId); log.trace("update user requested with id: {} and body {}", userId, updateUserDto); return userService.update(updateUserDto); From d6ff9cb6ec44d4ef07fd6beb6c1d9613eafcf3c6 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sun, 26 Oct 2025 10:16:05 +0300 Subject: [PATCH 14/15] chore: moved error dto's and handler in their packages --- .../shareit/exception/{ => dto}/ErrorResponse.java | 2 +- .../exception/{ => dto}/ValidationErrorResponse.java | 2 +- .../practicum/shareit/exception/{ => dto}/Violation.java | 2 +- .../exception/{ => handler}/GlobalExceptionHandler.java | 8 +++++++- 4 files changed, 10 insertions(+), 4 deletions(-) rename src/main/java/ru/practicum/shareit/exception/{ => dto}/ErrorResponse.java (58%) rename src/main/java/ru/practicum/shareit/exception/{ => dto}/ValidationErrorResponse.java (68%) rename src/main/java/ru/practicum/shareit/exception/{ => dto}/Violation.java (58%) rename src/main/java/ru/practicum/shareit/exception/{ => handler}/GlobalExceptionHandler.java (88%) diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/src/main/java/ru/practicum/shareit/exception/dto/ErrorResponse.java similarity index 58% rename from src/main/java/ru/practicum/shareit/exception/ErrorResponse.java rename to src/main/java/ru/practicum/shareit/exception/dto/ErrorResponse.java index f45d074..4e2e596 100644 --- a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java +++ b/src/main/java/ru/practicum/shareit/exception/dto/ErrorResponse.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.shareit.exception.dto; public record ErrorResponse(String name, String message) { } diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationErrorResponse.java b/src/main/java/ru/practicum/shareit/exception/dto/ValidationErrorResponse.java similarity index 68% rename from src/main/java/ru/practicum/shareit/exception/ValidationErrorResponse.java rename to src/main/java/ru/practicum/shareit/exception/dto/ValidationErrorResponse.java index 71d0271..497330f 100644 --- a/src/main/java/ru/practicum/shareit/exception/ValidationErrorResponse.java +++ b/src/main/java/ru/practicum/shareit/exception/dto/ValidationErrorResponse.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.shareit.exception.dto; import java.util.List; diff --git a/src/main/java/ru/practicum/shareit/exception/Violation.java b/src/main/java/ru/practicum/shareit/exception/dto/Violation.java similarity index 58% rename from src/main/java/ru/practicum/shareit/exception/Violation.java rename to src/main/java/ru/practicum/shareit/exception/dto/Violation.java index 68c9f12..e01906e 100644 --- a/src/main/java/ru/practicum/shareit/exception/Violation.java +++ b/src/main/java/ru/practicum/shareit/exception/dto/Violation.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.shareit.exception.dto; public record Violation(String fieldName, String message) { } diff --git a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java similarity index 88% rename from src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java rename to src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java index 61a234f..8e95630 100644 --- a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java +++ b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.shareit.exception.handler; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; @@ -7,6 +7,12 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.practicum.shareit.exception.DuplicateDataException; +import ru.practicum.shareit.exception.ForbiddenAccessException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.dto.ErrorResponse; +import ru.practicum.shareit.exception.dto.ValidationErrorResponse; +import ru.practicum.shareit.exception.dto.Violation; import java.util.List; import java.util.stream.Collectors; From 78cf24c1f8ab68608d1fe624fd4571c6359c1f22 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Sun, 26 Oct 2025 10:18:59 +0300 Subject: [PATCH 15/15] fix: moved logic of empty search query to service --- .../shareit/item/ItemServiceImpl.java | 5 ++++ .../repository/InMemoryItemRepository.java | 23 ++++--------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java index 13fb79a..13609f2 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java @@ -14,6 +14,7 @@ import ru.practicum.shareit.user.repository.UserRepository; import java.util.Collection; +import java.util.Collections; @Slf4j @Service @@ -65,6 +66,10 @@ public ItemDto updateItem(long userId, long itemId, UpdateItemDto newItem) { @Override public Collection searchItems(String query) { + if (query.isBlank()) { + return Collections.emptyList(); + } + return itemRepository.searchItems(query) .stream() .map(ItemMapper::toItemDto) diff --git a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java index d316585..a74f783 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java @@ -3,7 +3,10 @@ import org.springframework.stereotype.Repository; import ru.practicum.shareit.item.model.Item; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; @Repository public class InMemoryItemRepository implements ItemRepository { @@ -11,19 +14,7 @@ public class InMemoryItemRepository implements ItemRepository { @Override public Optional findById(long id) { - Item item = items.get(id); - if (item == null) { - return Optional.empty(); - } - - return Optional.of(new Item( - item.getId(), - item.getName(), - item.getDescription(), - item.isAvailable(), - item.getOwner(), - item.getRequest() - )); + return Optional.ofNullable(items.get(id)); } @Override @@ -48,10 +39,6 @@ public Item save(Item item) { public Collection searchItems(String query) { String lowercaseQuery = query.trim().toLowerCase(); - if (lowercaseQuery.isEmpty()) { - return Collections.emptyList(); - } - return items.values() .stream() .filter(Item::isAvailable)