From b6fc327ab483f22694d4c0b7ce0bfc572d90203e Mon Sep 17 00:00:00 2001 From: russuAV Date: Tue, 27 May 2025 14:22:11 +0500 Subject: [PATCH 1/2] =?UTF-8?q?=E2=80=94=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20UserController=20=E2=80=94=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20ItemController=20?= =?UTF-8?q?=E2=80=94=20=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D1=8B=20DTO-?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=8B=20=D0=B8=20=D0=BC=D0=B0?= =?UTF-8?q?=D0=BF=D0=BF=D0=B5=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20Item=20?= =?UTF-8?q?=D0=B8=20User=20=E2=80=94=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B8=D1=81=D1=8B=20=D0=B8=20=D1=80=D0=B5=D0=BF=D0=BE=D0=B7?= =?UTF-8?q?=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D0=B2=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BC=D1=8F=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ru/practicum/shareit/ShareItApp.java | 3 +- .../ru/practicum/shareit/booking/Booking.java | 26 ++++- .../exception/InternalServerException.java | 11 ++ .../shareit/exception/NotFoundException.java | 7 ++ .../exception/ValidationException.java | 7 ++ .../handler/GlobalExceptionHandler.java | 73 ++++++++++++ .../shareit/item/ItemController.java | 52 ++++++++- .../practicum/shareit/item/dto/ItemDto.java | 22 +++- .../shareit/item/mapper/ItemMapper.java | 42 +++++++ .../ru/practicum/shareit/item/model/Item.java | 18 ++- .../shareit/item/service/ItemService.java | 18 +++ .../shareit/item/service/ItemServiceImpl.java | 91 +++++++++++++++ .../shareit/request/ItemRequest.java | 15 ++- .../shareit/response/ErrorResponse.java | 18 +++ .../java/ru/practicum/shareit/user/User.java | 7 -- .../shareit/user/UserController.java | 51 ++++++-- .../practicum/shareit/user/dto/UserDto.java | 18 +++ .../shareit/user/mapper/UserMapper.java | 33 ++++++ .../ru/practicum/shareit/user/model/User.java | 12 ++ .../shareit/user/service/UserService.java | 18 +++ .../shareit/user/service/UserServiceImpl.java | 109 ++++++++++++++++++ 21 files changed, 613 insertions(+), 38 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/exception/InternalServerException.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/exception/handler/GlobalExceptionHandler.java create mode 100644 src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java create mode 100644 src/main/java/ru/practicum/shareit/item/service/ItemService.java create mode 100644 src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java create mode 100644 src/main/java/ru/practicum/shareit/response/ErrorResponse.java delete mode 100644 src/main/java/ru/practicum/shareit/user/User.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/UserDto.java create mode 100644 src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java create mode 100644 src/main/java/ru/practicum/shareit/user/model/User.java create mode 100644 src/main/java/ru/practicum/shareit/user/service/UserService.java create mode 100644 src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java index a00ad56..1137780 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/src/main/java/ru/practicum/shareit/ShareItApp.java @@ -9,5 +9,4 @@ public class ShareItApp { public static void main(String[] args) { SpringApplication.run(ShareItApp.class, args); } - -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java index 2d9c666..00bca60 100644 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/Booking.java @@ -1,7 +1,25 @@ package ru.practicum.shareit.booking; -/** - * TODO Sprint add-bookings. - */ +import lombok.Data; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Data public class Booking { -} + + public enum Status { + WAITING, + APPROVED, + REJECTED, + CANCELED + } + + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private Item item; + private User booker; + private Status status; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/InternalServerException.java b/src/main/java/ru/practicum/shareit/exception/InternalServerException.java new file mode 100644 index 0000000..f8bc39e --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/InternalServerException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) +public class InternalServerException extends RuntimeException { + public InternalServerException(String message) { + super(message); + } +} \ 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 0000000..98610d2 --- /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); + } +} \ No newline at end of file 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 0000000..067eded --- /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); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..667e417 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,73 @@ +package ru.practicum.shareit.exception.handler; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.response.ErrorResponse; + +import java.time.LocalDateTime; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFound(NotFoundException e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.NOT_FOUND.value(), + "Not Found", + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + } + + @ExceptionHandler({ValidationException.class}) + public ResponseEntity handleValidation( + ValidationException e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.CONFLICT.value(), + "409", + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleOtherExceptions(Exception e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.INTERNAL_SERVER_ERROR.value(), + "Internal Server Error", + "Произошла ошибка: " + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e, + HttpServletRequest request) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .findFirst() + .orElse("Ошибка валидации"); + + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.BAD_REQUEST.value(), + "Bad Request", + message, + request.getRequestURI() + ); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668..4c9b176 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 org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.item.service.ItemService; + +import java.util.List; -/** - * TODO Sprint add-controllers. - */ @RestController @RequestMapping("/items") +@RequiredArgsConstructor public class ItemController { -} + + private final ItemService itemService; + + private static final String USER_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ItemDto create(@RequestHeader(USER_HEADER) Long userId, + @Valid @RequestBody ItemDto itemDto) { + return ItemMapper.toItemDto(itemService.create(userId, itemDto)); + } + + @PatchMapping("/{itemId}") + public ItemDto update(@RequestHeader(USER_HEADER) Long userId, + @PathVariable Long itemId, + @RequestBody ItemDto dto) { + return ItemMapper.toItemDto(itemService.update(userId, itemId, dto)); + } + + @GetMapping("/{itemId}") + public ItemDto getById(@PathVariable Long itemId) { + return ItemMapper.toItemDto(itemService.getItemById(itemId)); + } + + @GetMapping + public List getAllByOwner(@RequestHeader(USER_HEADER) Long userId) { + return itemService.getAllByOwner(userId).stream() + .map(ItemMapper::toItemDto) + .toList(); + } + + @GetMapping("/search") + public List search(@RequestParam String text) { + return itemService.search(text).stream() + .map(ItemMapper::toItemDto) + .toList(); + } +} \ 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 9319d7d..31c26a4 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,21 @@ 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; + + @NotNull + @NotBlank + private String name; + @NotNull + 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/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java new file mode 100644 index 0000000..36b7fbc --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -0,0 +1,42 @@ +package ru.practicum.shareit.item.mapper; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.user.model.User; + +public class ItemMapper { + + public static ItemDto toItemDto(Item item) { + return ItemDto.builder() + .id(item.getId()) + .name(item.getName()) + .description(item.getDescription()) + .available(item.getAvailable()) + .requestId(item.getRequest() != null ? item.getRequest().getId() : null) + .build(); + } + + public static Item toItem(ItemDto itemDto, User owner, ItemRequest itemRequest) { + return Item.builder() + .name(itemDto.getName()) + .description(itemDto.getDescription()) + .available(itemDto.getAvailable()) + .owner(owner) + .request(itemRequest) + .build(); + } + + public static Item updateItemFields(Item item, ItemDto itemDto) { + if (itemDto.getDescription() != null) { + item.setDescription(itemDto.getDescription()); + } + if (itemDto.getName() != null) { + item.setName(itemDto.getName()); + } + if (itemDto.getAvailable() != null) { + item.setAvailable(itemDto.getAvailable()); + } + return item; + } +} \ 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 44eb73d..da711b3 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,17 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import lombok.Builder; +import lombok.Data; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.user.model.User; + +@Data +@Builder public class Item { -} + private Long id; + private String name; + private String description; + private Boolean available; + private User owner; + private ItemRequest request; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java new file mode 100644 index 0000000..4cba349 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.service; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; + +import java.util.List; + +public interface ItemService { + Item create(Long userId, ItemDto itemDto); + + Item update(Long userId, Long itemId, ItemDto itemDto); + + Item getItemById(Long itemId); + + List getAllByOwner(Long userId); + + List search(String text); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java new file mode 100644 index 0000000..1fa33f7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,91 @@ +package ru.practicum.shareit.item.service; + +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.mapper.ItemMapper; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +public class ItemServiceImpl implements ItemService { + private final UserService userService; + private final Map items = new HashMap<>(); + + @Override + public Item create(Long userId, ItemDto itemDto) { + User owner = userService.getUserById(userId); + Item item = ItemMapper.toItem(itemDto, owner, null); // пока request == null + item.setId(getNextId()); + items.put(item.getId(), item); + + log.info("Вещь создана: {}", item); + return item; + } + + @Override + public Item update(Long userId, Long itemId, ItemDto itemDto) { + Item item = getItemById(itemId); + if (!item.getOwner().getId().equals(userId)) { + throw new NotFoundException("Редактировать может только владелец."); + } + + Item update = ItemMapper.updateItemFields(item, itemDto); + log.info("Вещь обновлена: {}", item); + return update; + } + + @Override + public Item getItemById(Long itemId) { + Item item = items.get(itemId); + if (item == null) { + log.error("Вещь с id {} не найдена.", itemId); + throw new NotFoundException("Вещь с id " + itemId + " не найдена."); + } + + log.info("Вещь с id {} получена.", itemId); + return item; + } + + @Override + public List getAllByOwner(Long userId) { + List itemsByOwner = items.values().stream() + .filter(i -> i.getOwner().getId().equals(userId)) + .toList(); + + log.info("Получен список всех вещей, сдаваемых пользователем с ID {}", userId); + return itemsByOwner; + } + + @Override + public List search(String text) { + if (text == null || text.isBlank()) { + log.info("По данному тексту ничего не найдено."); + return Collections.emptyList(); + } + String lower = text.toLowerCase(); + return items.values().stream() + .filter(i -> Boolean.TRUE.equals(i.getAvailable())) + .filter(i -> i.getName().toLowerCase().contains(lower) || + i.getDescription().toLowerCase().contains(lower)) + .collect(Collectors.toList()); + } + + + public long getNextId() { + long currentMaxId = items.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java index 95d6f23..5110f03 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -1,7 +1,14 @@ package ru.practicum.shareit.request; -/** - * TODO Sprint add-item-requests. - */ +import lombok.Data; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Data public class ItemRequest { -} + private Long id; + private String description; + private User requester; + private LocalDateTime created; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/response/ErrorResponse.java b/src/main/java/ru/practicum/shareit/response/ErrorResponse.java new file mode 100644 index 0000000..21b3444 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/response/ErrorResponse.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Setter +@Getter +@AllArgsConstructor +public class ErrorResponse { + private LocalDateTime timestamp; + private int status; + private String error; + private String message; + private String path; +} \ 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 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/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index 03039b9..4244c8a 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,49 @@ 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 org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mapper.UserMapper; +import ru.practicum.shareit.user.service.UserService; + +import java.util.List; -/** - * TODO Sprint add-controllers. - */ @RestController -@RequestMapping(path = "/users") +@RequestMapping("/users") +@RequiredArgsConstructor public class UserController { -} + + private final UserService userService; + + @PostMapping + public UserDto create(@Valid @RequestBody UserDto userDto) { + return UserMapper.toUserDto(userService.create(userDto)); + } + + @PatchMapping("/{id}") + public UserDto update(@PathVariable Long id, + @RequestBody UserDto userDto) { + userDto.setId(id); + return UserMapper.toUserDto(userService.update(userDto)); + } + + @GetMapping("/{id}") + public UserDto getById(@PathVariable Long id) { + return UserMapper.toUserDto(userService.getUserById(id)); + } + + @GetMapping + public List getAll() { + return userService.getAll().stream() + .map(UserMapper::toUserDto) + .toList(); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + userService.delete(id); + } +} \ 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 0000000..f7a36fd --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class UserDto { + private Long id; + + @Email(message = "Некорректный email") + @NotNull + private String email; + + private String name; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java new file mode 100644 index 0000000..cb7e312 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -0,0 +1,33 @@ +package ru.practicum.shareit.user.mapper; + +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.dto.UserDto; + +public class UserMapper { + + public static UserDto toUserDto(User user) { + return UserDto.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + public static User toUser(UserDto userDto) { + return User.builder() + .name(userDto.getName()) + .email(userDto.getEmail()) + .build(); + } + + public static User updateUserFields(User user, UserDto userDto) { + if (userDto.getEmail() != null) { + user.setEmail(userDto.getEmail()); + } + if (userDto.getName() != null) { + user.setName(userDto.getName()); + } + + return user; + } +} \ No newline at end of file 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..9a04c58 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.user.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class User { + private Long id; + private String email; + private String name; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/src/main/java/ru/practicum/shareit/user/service/UserService.java new file mode 100644 index 0000000..658346a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.List; + +public interface UserService { + User create(UserDto userDto); + + User update(UserDto userDto); + + User getUserById(Long id); + + List getAll(); + + void delete(Long id); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java new file mode 100644 index 0000000..b04ff3a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,109 @@ +package ru.practicum.shareit.user.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.mapper.UserMapper; + +import java.util.*; + +@Service +@Slf4j +public class UserServiceImpl implements UserService { + private final Map users = new HashMap<>(); + private final Map usersByEmail = new HashMap<>(); + + @Override + public User create(UserDto userDto) { + if (users.containsKey(userDto.getId())) { + log.error("Ошибка создания пользователя: id {} уже используется", userDto.getId()); + throw new ValidationException("Пользователь уже зарегистрирован"); + } + if (usersByEmail.containsKey(userDto.getEmail())) { + log.error("Ошибка создания пользователя: email {} уже используется", userDto.getEmail()); + throw new ValidationException("Данный email уже используется"); + } + + Long id = getNextId(); + User user = UserMapper.toUser(userDto); + user.setId(id); + users.put(id, user); + usersByEmail.put(user.getEmail(), user); + + log.info("Пользователь успешно создан с id: {}, email: {}", + user.getId(), user.getEmail()); + return user; + } + + @Override + public User update(UserDto userDto) { + if (userDto.getId() == null || userDto.getId() == 0) { + throw new ValidationException("id должен быть указан"); + } + User userWithOldData = users.get(userDto.getId()); + if (userWithOldData == null) { + log.error("Пользователь не найден"); + throw new NotFoundException("Пользователь не найден"); + } + // проверяем доступность email + if (userDto.getEmail() != null + && !userDto.getEmail().equals(userWithOldData.getEmail())) { + if (usersByEmail.containsKey(userDto.getEmail())) { + User existingUserByEmail = usersByEmail.get(userDto.getEmail()); + if (!existingUserByEmail.getId().equals(userDto.getId())) { + log.error("Ошибка обновления: email {} уже используется", userDto.getEmail()); + throw new ValidationException("Этот e-mail уже используется"); + } + } + } + + User updateUser = UserMapper.updateUserFields(userWithOldData, userDto); + + // удаляем старого пользователя и возвращаем нового + users.remove(userWithOldData.getId()); + usersByEmail.remove(userWithOldData.getEmail()); + users.put(userDto.getId(), updateUser); + usersByEmail.put(userDto.getEmail(), updateUser); + + log.info("Пользователь с id {} успешно обновлён", userWithOldData.getId()); + return updateUser; + } + + @Override + public User getUserById(Long userId) { + User user = users.get(userId); + if (user == null) { + log.error("Пользователь с id {} не найден.", userId); + throw new NotFoundException("Пользователь с id " + userId + " не найден."); + } + + log.info("Пользователь с id {} получен.", userId); + return user; + } + + @Override + public List getAll() { + return users.values().stream() + .toList(); + } + + @Override + public void delete(Long userId) { + User user = getUserById(userId); // один вызов + users.remove(userId); + usersByEmail.remove(user.getEmail()); + log.info("Пользователь с id {} удален", userId); + } + + public long getNextId() { + long currentMaxId = users.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} \ No newline at end of file From d86a779740af8f175f734a1b97b3f3a433b2117d Mon Sep 17 00:00:00 2001 From: russuAV Date: Sat, 31 May 2025 19:05:45 +0500 Subject: [PATCH 2/2] =?UTF-8?q?-=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81=20HTT?= =?UTF-8?q?P-=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=B0=20=D0=B2=20ValidationExc?= =?UTF-8?q?eption=20-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=20ConflictException=20-=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=BD=D0=B0=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8E=20ID=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=20AtomicLong=20-=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D1=81=D1=80=D0=B0=D0=B2=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20(=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=20equals)=20-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20-=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F=20(update)=20-=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B2=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=B8=D1=85=20=D0=B8=D0=B4=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=BE=D1=80=D1=8B=20=D0=B2?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8F=D1=85=20=D0=BC=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/practicum/shareit/booking/Booking.java | 7 +-- .../shareit/exception/ConflictException.java | 7 +++ .../handler/GlobalExceptionHandler.java | 16 ++++++- .../shareit/item/mapper/ItemMapper.java | 2 +- .../ru/practicum/shareit/item/model/Item.java | 3 +- .../shareit/item/service/ItemServiceImpl.java | 25 +++++----- .../shareit/request/ItemRequest.java | 4 +- .../practicum/shareit/user/dto/UserDto.java | 1 - .../shareit/user/mapper/UserMapper.java | 7 +-- .../shareit/user/service/UserServiceImpl.java | 48 ++++++++----------- 10 files changed, 59 insertions(+), 61 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/exception/ConflictException.java diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java index 00bca60..bbb99f8 100644 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/Booking.java @@ -1,9 +1,6 @@ package ru.practicum.shareit.booking; import lombok.Data; -import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.user.model.User; - import java.time.LocalDateTime; @Data @@ -19,7 +16,7 @@ public enum Status { private Long id; private LocalDateTime start; private LocalDateTime end; - private Item item; - private User booker; + private Long itemId; + private Long bookerId; private Status status; } \ No newline at end of file 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 0000000..12f8fe0 --- /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); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java index 667e417..dec3dd0 100644 --- a/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/ru/practicum/shareit/exception/handler/GlobalExceptionHandler.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.practicum.shareit.exception.ConflictException; import ru.practicum.shareit.exception.NotFoundException; import ru.practicum.shareit.exception.ValidationException; import ru.practicum.shareit.response.ErrorResponse; @@ -30,10 +31,23 @@ public ResponseEntity handleNotFound(NotFoundException e, HttpSer @ExceptionHandler({ValidationException.class}) public ResponseEntity handleValidation( ValidationException e, HttpServletRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.BAD_REQUEST.value(), + "Bad Request", + e.getMessage(), + request.getRequestURI() + ); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } + + @ExceptionHandler({ConflictException.class}) + public ResponseEntity handleConflict( + ConflictException e, HttpServletRequest request) { ErrorResponse errorResponse = new ErrorResponse( LocalDateTime.now(), HttpStatus.CONFLICT.value(), - "409", + "Conflict", e.getMessage(), request.getRequestURI() ); diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java index 36b7fbc..c7ce218 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -22,7 +22,7 @@ public static Item toItem(ItemDto itemDto, User owner, ItemRequest itemRequest) .name(itemDto.getName()) .description(itemDto.getDescription()) .available(itemDto.getAvailable()) - .owner(owner) + .ownerId(owner.getId()) .request(itemRequest) .build(); } 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 da711b3..f0e6ad2 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -3,7 +3,6 @@ import lombok.Builder; import lombok.Data; import ru.practicum.shareit.request.ItemRequest; -import ru.practicum.shareit.user.model.User; @Data @Builder @@ -12,6 +11,6 @@ public class Item { private String name; private String description; private Boolean available; - private User owner; + private Long ownerId; private ItemRequest request; } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java index 1fa33f7..42542c7 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -11,7 +11,7 @@ import ru.practicum.shareit.user.service.UserService; import java.util.*; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicLong; @Service @Slf4j @@ -19,6 +19,7 @@ public class ItemServiceImpl implements ItemService { private final UserService userService; private final Map items = new HashMap<>(); + private final AtomicLong itemIdCounter = new AtomicLong(0); @Override public Item create(Long userId, ItemDto itemDto) { @@ -34,7 +35,7 @@ public Item create(Long userId, ItemDto itemDto) { @Override public Item update(Long userId, Long itemId, ItemDto itemDto) { Item item = getItemById(itemId); - if (!item.getOwner().getId().equals(userId)) { + if (!Objects.equals(item.getOwnerId(), userId)) { throw new NotFoundException("Редактировать может только владелец."); } @@ -58,7 +59,7 @@ public Item getItemById(Long itemId) { @Override public List getAllByOwner(Long userId) { List itemsByOwner = items.values().stream() - .filter(i -> i.getOwner().getId().equals(userId)) + .filter(i -> i.getOwnerId().equals(userId)) .toList(); log.info("Получен список всех вещей, сдаваемых пользователем с ID {}", userId); @@ -68,24 +69,22 @@ public List getAllByOwner(Long userId) { @Override public List search(String text) { if (text == null || text.isBlank()) { - log.info("По данному тексту ничего не найдено."); - return Collections.emptyList(); + log.info("Пустой запрос для поиска"); + return List.of(); } + return findAvailableItemsByText(text); + } + + public List findAvailableItemsByText(String text) { String lower = text.toLowerCase(); return items.values().stream() .filter(i -> Boolean.TRUE.equals(i.getAvailable())) .filter(i -> i.getName().toLowerCase().contains(lower) || i.getDescription().toLowerCase().contains(lower)) - .collect(Collectors.toList()); + .toList(); } - public long getNextId() { - long currentMaxId = items.keySet() - .stream() - .mapToLong(id -> id) - .max() - .orElse(0); - return ++currentMaxId; + return itemIdCounter.incrementAndGet(); } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java index 5110f03..fe7a595 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -1,14 +1,12 @@ package ru.practicum.shareit.request; import lombok.Data; -import ru.practicum.shareit.user.model.User; - import java.time.LocalDateTime; @Data public class ItemRequest { private Long id; private String description; - private User requester; + private Long requesterId; private LocalDateTime created; } \ 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 index f7a36fd..e956bd0 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -13,6 +13,5 @@ public class UserDto { @Email(message = "Некорректный email") @NotNull private String email; - private String name; } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java index cb7e312..b673bc4 100644 --- a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java +++ b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -20,14 +20,9 @@ public static User toUser(UserDto userDto) { .build(); } - public static User updateUserFields(User user, UserDto userDto) { - if (userDto.getEmail() != null) { - user.setEmail(userDto.getEmail()); - } + public static void updateUserFields(User user, UserDto userDto) { if (userDto.getName() != null) { user.setName(userDto.getName()); } - - return user; } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java index b04ff3a..19d63c8 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -2,6 +2,7 @@ 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.model.User; @@ -9,28 +10,25 @@ import ru.practicum.shareit.user.mapper.UserMapper; import java.util.*; +import java.util.concurrent.atomic.AtomicLong; @Service @Slf4j public class UserServiceImpl implements UserService { private final Map users = new HashMap<>(); private final Map usersByEmail = new HashMap<>(); + private final AtomicLong userIdCounter = new AtomicLong(0); @Override public User create(UserDto userDto) { - if (users.containsKey(userDto.getId())) { - log.error("Ошибка создания пользователя: id {} уже используется", userDto.getId()); - throw new ValidationException("Пользователь уже зарегистрирован"); - } if (usersByEmail.containsKey(userDto.getEmail())) { log.error("Ошибка создания пользователя: email {} уже используется", userDto.getEmail()); - throw new ValidationException("Данный email уже используется"); + throw new ConflictException("Данный email уже используется"); } - Long id = getNextId(); User user = UserMapper.toUser(userDto); - user.setId(id); - users.put(id, user); + user.setId(getNextId()); + users.put(user.getId(), user); usersByEmail.put(user.getEmail(), user); log.info("Пользователь успешно создан с id: {}, email: {}", @@ -43,33 +41,30 @@ public User update(UserDto userDto) { if (userDto.getId() == null || userDto.getId() == 0) { throw new ValidationException("id должен быть указан"); } - User userWithOldData = users.get(userDto.getId()); - if (userWithOldData == null) { + User existingUser = users.get(userDto.getId()); + if (existingUser == null) { log.error("Пользователь не найден"); throw new NotFoundException("Пользователь не найден"); } // проверяем доступность email - if (userDto.getEmail() != null - && !userDto.getEmail().equals(userWithOldData.getEmail())) { + if (!Objects.equals(userDto.getEmail(), existingUser.getEmail())) { if (usersByEmail.containsKey(userDto.getEmail())) { User existingUserByEmail = usersByEmail.get(userDto.getEmail()); - if (!existingUserByEmail.getId().equals(userDto.getId())) { + if (!Objects.equals(existingUserByEmail.getId(), userDto.getId())) { log.error("Ошибка обновления: email {} уже используется", userDto.getEmail()); - throw new ValidationException("Этот e-mail уже используется"); + throw new ConflictException("Этот e-mail уже используется"); } } + usersByEmail.remove(existingUser.getEmail()); + existingUser.setEmail(userDto.getEmail()); + usersByEmail.put(existingUser.getEmail(), existingUser); } - User updateUser = UserMapper.updateUserFields(userWithOldData, userDto); - - // удаляем старого пользователя и возвращаем нового - users.remove(userWithOldData.getId()); - usersByEmail.remove(userWithOldData.getEmail()); - users.put(userDto.getId(), updateUser); - usersByEmail.put(userDto.getEmail(), updateUser); + // Обновляем только изменяемые поля + UserMapper.updateUserFields(existingUser, userDto); - log.info("Пользователь с id {} успешно обновлён", userWithOldData.getId()); - return updateUser; + log.info("Пользователь с id {} успешно обновлён", existingUser.getId()); + return existingUser; } @Override @@ -99,11 +94,6 @@ public void delete(Long userId) { } public long getNextId() { - long currentMaxId = users.keySet() - .stream() - .mapToLong(id -> id) - .max() - .orElse(0); - return ++currentMaxId; + return userIdCounter.incrementAndGet(); } } \ No newline at end of file