From a567834ea2316738da18a9fe8edc7d46515c2ae3 Mon Sep 17 00:00:00 2001 From: Evgeniy Date: Sun, 22 Mar 2026 01:36:04 +0700 Subject: [PATCH 1/2] sprint 14 add-controllers --- .../shareit/exception/ConflictException.java | 7 ++ .../shareit/exception/ErrorHandler.java | 41 ++++++++ .../shareit/exception/NotFoundException.java | 7 ++ .../exception/ValidationException.java | 7 ++ .../shareit/item/ItemController.java | 52 ++++++++-- .../ru/practicum/shareit/item/ItemMapper.java | 25 +++++ .../practicum/shareit/item/ItemService.java | 16 +++ .../shareit/item/ItemServiceImpl.java | 97 +++++++++++++++++++ .../practicum/shareit/item/dto/ItemDto.java | 20 +++- .../ru/practicum/shareit/item/model/Item.java | 21 +++- .../java/ru/practicum/shareit/user/User.java | 17 +++- .../shareit/user/UserController.java | 49 ++++++++-- .../ru/practicum/shareit/user/UserMapper.java | 21 ++++ .../practicum/shareit/user/UserService.java | 16 +++ .../shareit/user/UserServiceImpl.java | 80 +++++++++++++++ .../practicum/shareit/user/dto/UserDto.java | 17 ++++ 16 files changed, 468 insertions(+), 25 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/exception/ConflictException.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ErrorHandler.java create mode 100644 src/main/java/ru/practicum/shareit/exception/NotFoundException.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ValidationException.java create mode 100644 src/main/java/ru/practicum/shareit/item/ItemMapper.java create mode 100644 src/main/java/ru/practicum/shareit/item/ItemService.java create mode 100644 src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java create mode 100644 src/main/java/ru/practicum/shareit/user/UserMapper.java create mode 100644 src/main/java/ru/practicum/shareit/user/UserService.java create mode 100644 src/main/java/ru/practicum/shareit/user/UserServiceImpl.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/UserDto.java diff --git a/src/main/java/ru/practicum/shareit/exception/ConflictException.java b/src/main/java/ru/practicum/shareit/exception/ConflictException.java new file mode 100644 index 00000000..78851230 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ConflictException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java new file mode 100644 index 00000000..7c5bb444 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -0,0 +1,41 @@ +package ru.practicum.shareit.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Map catchAllExceptions(Exception ex) { + log.error("500: ", ex); + return Map.of("error", "Произошла ошибка"); + } + + @ExceptionHandler({ValidationException.class, MethodArgumentNotValidException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map catchValidationErrors(Exception ex) { + log.error("400: {}", ex.getMessage()); + return Map.of("error", "Ошибка валидации"); + } + + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public Map catchConflictStatus(ConflictException ex) { + log.error("409: {}", ex.getMessage()); + return Map.of("error", ex.getMessage()); + } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public Map catchNotFoundStatus(NotFoundException ex) { + log.error("404: {}", ex.getMessage()); + return Map.of("error", ex.getMessage()); + } +} \ No newline at end of file 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 00000000..508b5456 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/src/main/java/ru/practicum/shareit/exception/ValidationException.java new file mode 100644 index 00000000..59043da1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668b..853d3444 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,52 @@ 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.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; -/** - * TODO Sprint add-controllers. - */ +import java.util.List; + +@Slf4j @RestController @RequestMapping("/items") +@RequiredArgsConstructor public class ItemController { -} + + private final ItemService itemService; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @GetMapping + public List fetchAllUserItems(@RequestHeader(USER_ID_HEADER) Long ownerId) { + log.info("GET /items (владелец id={})", ownerId); + return itemService.getByOwner(ownerId); + } + + @GetMapping("/search") + public List findItems(@RequestParam(name = "text") String query) { + log.info("GET /items/search?text={}", query); + return itemService.search(query); + } + + @PatchMapping("/{itemId}") + public ItemDto modifyItem(@RequestHeader(USER_ID_HEADER) Long requesterId, + @PathVariable(name = "itemId") Long id, + @RequestBody ItemDto payload) { + log.info("PATCH /items/{} от пользователя id={}", id, requesterId); + return itemService.update(requesterId, id, payload); + } + + @PostMapping + public ItemDto addNewItem(@RequestHeader(USER_ID_HEADER) Long creatorId, + @Valid @RequestBody ItemDto payload) { + log.info("POST /items от пользователя id={}", creatorId); + return itemService.create(creatorId, payload); + } + + @GetMapping("/{itemId}") + public ItemDto fetchItemById(@PathVariable(name = "itemId") Long id) { + log.info("GET /items/{}", id); + return itemService.getById(id); + } +} \ No newline at end of file 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 00000000..11e611bf --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.item; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +public class ItemMapper { + public static Item toItem(ItemDto dto) { + return Item.builder() + .available(dto.getAvailable()) + .description(dto.getDescription()) + .name(dto.getName()) + .id(dto.getId()) + .build(); + } + + public static ItemDto toItemDto(Item entity) { + return ItemDto.builder() + .requestId(entity.getRequestId()) + .available(entity.getAvailable()) + .description(entity.getDescription()) + .name(entity.getName()) + .id(entity.getId()) + .build(); + } +} \ No newline at end of file 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 00000000..1b308318 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemService.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.item; + +import ru.practicum.shareit.item.dto.ItemDto; +import java.util.List; + +public interface ItemService { + ItemDto update(Long userId, Long itemId, ItemDto itemDto); + + List search(String text); + + ItemDto getById(Long itemId); + + ItemDto create(Long userId, ItemDto itemDto); + + List getByOwner(Long userId); +} \ No newline at end of file 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 00000000..cd6c86ff --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java @@ -0,0 +1,97 @@ +package ru.practicum.shareit.item; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.UserService; +import ru.practicum.shareit.user.UserMapper; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + + private final Map repository = new HashMap<>(); + private final UserService userService; + private Long lastGeneratedId = 1L; + + @Override + public List search(String searchStr) { + if (searchStr == null || searchStr.isBlank()) { + return Collections.emptyList(); + } + + String lowerCaseQuery = searchStr.toLowerCase(); + return repository.values().stream() + .filter(Item::getAvailable) + .filter(item -> item.getName().toLowerCase().contains(lowerCaseQuery) + || item.getDescription().toLowerCase().contains(lowerCaseQuery)) + .map(ItemMapper::toItemDto) + .collect(Collectors.toList()); + } + + @Override + public ItemDto getById(Long id) { + Item found = repository.get(id); + if (found == null) { + throw new NotFoundException("Объект с идентификатором " + id + " не существует"); + } + return ItemMapper.toItemDto(found); + } + + @Override + public List getByOwner(Long ownerId) { + userService.getById(ownerId); + + return repository.values().stream() + .filter(obj -> obj.getOwner().getId().equals(ownerId)) + .map(ItemMapper::toItemDto) + .collect(Collectors.toList()); + } + + @Override + public ItemDto update(Long userId, Long itemId, ItemDto dto) { + Item target = repository.get(itemId); + + if (Objects.isNull(target)) { + throw new NotFoundException("Вещь с id " + itemId + " не найдена"); + } + + if (!Objects.equals(target.getOwner().getId(), userId)) { + throw new NotFoundException("Доступ запрещен: редактировать может только владелец"); + } + + if (dto.getName() != null && !dto.getName().trim().isEmpty()) { + target.setName(dto.getName()); + } + if (dto.getDescription() != null && !dto.getDescription().trim().isEmpty()) { + target.setDescription(dto.getDescription()); + } + if (dto.getAvailable() != null) { + target.setAvailable(dto.getAvailable()); + } + + return ItemMapper.toItemDto(target); + } + + @Override + public ItemDto create(Long userId, ItemDto itemDto) { + User creator = UserMapper.toUser(userService.getById(userId)); + Item newEntry = ItemMapper.toItem(itemDto); + + newEntry.setId(lastGeneratedId++); + newEntry.setOwner(creator); + + repository.put(newEntry.getId(), newEntry); + log.info("Сохранена новая вещь с ID: {}", newEntry.getId()); + + return ItemMapper.toItemDto(newEntry); + } +} \ No newline at end of file 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 9319d7d7..1098f215 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,19 @@ package ru.practicum.shareit.item.dto; -/** - * TODO Sprint add-controllers. - */ +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder public class ItemDto { -} + private Long id; + @NotBlank + private String name; + @NotBlank + private String description; + @NotNull + private Boolean available; + private Long requestId; +} \ No newline at end of file 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 44eb73dd..dbb28048 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,20 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.user.User; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor public class Item { -} + private Long id; + private String name; + private String description; + private Boolean available; + private User owner; + private Long requestId; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/src/main/java/ru/practicum/shareit/user/User.java index ae6e7f33..69127b15 100644 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ b/src/main/java/ru/practicum/shareit/user/User.java @@ -1,7 +1,16 @@ package ru.practicum.shareit.user; -/** - * TODO Sprint add-controllers. - */ +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor public class User { -} + private String email; + private String name; + private Long id; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index 03039b9d..1391acf0 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,47 @@ 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.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; -/** - * TODO Sprint add-controllers. - */ +import java.util.List; + +@Slf4j @RestController -@RequestMapping(path = "/users") +@RequestMapping("/users") +@RequiredArgsConstructor public class UserController { -} + private final UserService userService; + + @DeleteMapping("/{id}") + public void removeUser(@PathVariable Long id) { + log.info("DELETE request for user ID: {}", id); + userService.delete(id); + } + + @GetMapping + public List getAllUsers() { + log.info("GET request for all users"); + return userService.findAll(); + } + + @PatchMapping("/{id}") + public UserDto patchUser(@PathVariable Long id, @RequestBody UserDto dto) { + log.info("PATCH request for user ID: {}", id); + return userService.update(id, dto); + } + + @GetMapping("/{id}") + public UserDto getUser(@PathVariable Long id) { + log.info("GET request for user ID: {}", id); + return userService.getById(id); + } + + @PostMapping + public UserDto saveUser(@Valid @RequestBody UserDto dto) { + log.info("POST request to create user: {}", dto.getEmail()); + return userService.create(dto); + } +} \ No newline at end of file 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 00000000..a4620b17 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.user; + +import ru.practicum.shareit.user.dto.UserDto; + +public class UserMapper { + public static User toUser(UserDto dto) { + return User.builder() + .email(dto.getEmail()) + .name(dto.getName()) + .id(dto.getId()) + .build(); + } + + public static UserDto toUserDto(User model) { + return UserDto.builder() + .name(model.getName()) + .email(model.getEmail()) + .id(model.getId()) + .build(); + } +} \ No newline at end of file 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 00000000..f92f2f6c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserService.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.user; + +import ru.practicum.shareit.user.dto.UserDto; +import java.util.List; + +public interface UserService { + void delete(Long id); + + List findAll(); + + UserDto update(Long id, UserDto userDto); + + UserDto getById(Long id); + + UserDto create(UserDto userDto); +} \ No newline at end of file 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 00000000..fb9aa035 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java @@ -0,0 +1,80 @@ +package ru.practicum.shareit.user; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.ConflictException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final Map dataStorage = new HashMap<>(); + private Long sequenceId = 1L; + + @Override + public void delete(Long id) { + dataStorage.remove(id); + } + + @Override + public List findAll() { + return dataStorage.values().stream() + .map(UserMapper::toUserDto) + .collect(Collectors.toList()); + } + + @Override + public UserDto getById(Long id) { + User user = dataStorage.get(id); + if (user == null) { + throw new NotFoundException("Пользователь не найден: " + id); + } + return UserMapper.toUserDto(user); + } + + @Override + public UserDto update(Long id, UserDto dto) { + User existingUser = dataStorage.get(id); + if (existingUser == null) { + throw new NotFoundException("Невозможно обновить: пользователь " + id + " не существует"); + } + + if (dto.getEmail() != null && !dto.getEmail().equalsIgnoreCase(existingUser.getEmail())) { + if (!dto.getEmail().contains("@")) { + throw new ValidationException("Некорректный формат email"); + } + validateEmailUniqueness(dto.getEmail(), id); + existingUser.setEmail(dto.getEmail()); + } + + if (dto.getName() != null && !dto.getName().trim().isEmpty()) { + existingUser.setName(dto.getName()); + } + + return UserMapper.toUserDto(existingUser); + } + + @Override + public UserDto create(UserDto dto) { + validateEmailUniqueness(dto.getEmail(), null); + User newUser = UserMapper.toUser(dto); + newUser.setId(sequenceId++); + dataStorage.put(newUser.getId(), newUser); + return UserMapper.toUserDto(newUser); + } + + private void validateEmailUniqueness(String email, Long currentUserId) { + boolean isDuplicate = dataStorage.values().stream() + .anyMatch(u -> u.getEmail().equalsIgnoreCase(email) && !u.getId().equals(currentUserId)); + if (isDuplicate) { + throw new ConflictException("Электронная почта " + email + " уже используется"); + } + } +} \ No newline at end of file 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 00000000..e2ec0803 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class UserDto { + @NotBlank + @Email + private String email; + @NotBlank + private String name; + private Long id; +} \ No newline at end of file From f774d26b1f1cf8e3db6aed55977058c3f13ea763 Mon Sep 17 00:00:00 2001 From: Evgeniy Date: Sun, 22 Mar 2026 02:13:12 +0700 Subject: [PATCH 2/2] 14 sprint --- src/main/java/ru/practicum/shareit/user/UserController.java | 2 +- 1 file changed, 1 insertion(+), 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 1391acf0..162e71da 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -7,7 +7,7 @@ import ru.practicum.shareit.user.dto.UserDto; import java.util.List; - +//use @Slf4j @RestController @RequestMapping("/users")