diff --git a/pom.xml b/pom.xml index 2db888c..853ff4e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,10 @@ spring-boot-configuration-processor true + + org.springframework.boot + spring-boot-starter-data-jpa + org.postgresql @@ -49,7 +53,8 @@ com.h2database h2 - test + + runtime org.springframework.boot diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java deleted file mode 100644 index d76494a..0000000 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ /dev/null @@ -1,28 +0,0 @@ -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; - -/** - * TODO Sprint add-bookings. - */ -@Data -public class Booking { - private Long id; - private LocalDateTime start; - private LocalDateTime end; - private Item item; - private User booker; - private Status status; - - enum Status { - WAITING, - APPROVED, - REJECTED, - CANCELED - } - -} diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java index b94493d..09b7983 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,12 +1,57 @@ package ru.practicum.shareit.booking; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.BookingDtoRequest; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; +import ru.practicum.shareit.booking.enumerated.State; +import ru.practicum.shareit.booking.service.BookingService; + +import java.util.List; -/** - * TODO Sprint add-bookings. - */ @RestController +@AllArgsConstructor @RequestMapping(path = "/bookings") public class BookingController { + + private final BookingService bookingService; + private final String userIdHeader = "X-Sharer-User-Id"; + + @PostMapping + public BookingDtoResponse createBooking( + @RequestHeader(userIdHeader) Long bookerId, + @Valid @RequestBody BookingDtoRequest bookingDtoRequest) { + return bookingService.createBooking(bookerId, bookingDtoRequest); + } + + @PatchMapping("/{bookingId}") + public BookingDtoResponse updateBooking( + @RequestHeader(userIdHeader) Long bookerId, + @RequestParam Boolean approved, + @PathVariable Long bookingId) { + return bookingService.updateBooking(bookerId, bookingId, approved); + } + + @GetMapping("/{bookingId}") + public BookingDtoResponse getBookingById( + @RequestHeader(userIdHeader) Long bookerId, + @PathVariable Long bookingId) { + return bookingService.getBookingById(bookerId, bookingId); + } + + @GetMapping + public List getBookingByUser( + @RequestHeader(userIdHeader) Long bookerId, + @RequestParam(defaultValue = "ALL") State state) { + return bookingService.getBookingByUser(bookerId, state); + } + + @GetMapping("/owner") + public List getBookingByItemsUser( + @RequestHeader(userIdHeader) Long userOwnerItemId, + @RequestParam(defaultValue = "ALL") State state) { + return bookingService.getBookingByItemsUser(userOwnerItemId, state); + } + } diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java deleted file mode 100644 index 861de9e..0000000 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.booking.dto; - -/** - * TODO Sprint add-bookings. - */ -public class BookingDto { -} diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoRequest.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoRequest.java new file mode 100644 index 0000000..de14e85 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoRequest.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.booking.dto; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import ru.practicum.shareit.booking.enumerated.Status; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class BookingDtoRequest { + + @FutureOrPresent(message = "Дата начала бронирования не может быть раньше текущего времени") + @NotNull(message = "Дата начала бронирования не может быть пустой") + private LocalDateTime start; + @Future(message = "Дата окончания бронирования не может быть раньше текущего времени") + @NotNull(message = "Дата окончания бронирования не может быть пустой") + private LocalDateTime end; + @NotNull(message = "ID вещи не может быть пустым") + private Long itemId; + private Status status; +} diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java new file mode 100644 index 0000000..8c25e45 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDtoResponse.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import ru.practicum.shareit.booking.enumerated.Status; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class BookingDtoResponse { + + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private ItemDto item; + private UserDto booker; + private Status status; +} diff --git a/src/main/java/ru/practicum/shareit/booking/enumerated/State.java b/src/main/java/ru/practicum/shareit/booking/enumerated/State.java new file mode 100644 index 0000000..54b2467 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/enumerated/State.java @@ -0,0 +1,5 @@ +package ru.practicum.shareit.booking.enumerated; + +public enum State { + ALL, CURRENT, PAST, FUTURE, WAITING, REJECTED, UNSUPPORTED_STATUS +} diff --git a/src/main/java/ru/practicum/shareit/booking/enumerated/Status.java b/src/main/java/ru/practicum/shareit/booking/enumerated/Status.java new file mode 100644 index 0000000..3a1adab --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/enumerated/Status.java @@ -0,0 +1,5 @@ +package ru.practicum.shareit.booking.enumerated; + +public enum Status { + WAITING, APPROVED, REJECTED, CANCELED +} diff --git a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java new file mode 100644 index 0000000..1884159 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java @@ -0,0 +1,33 @@ +package ru.practicum.shareit.booking.mapper; + +import ru.practicum.shareit.booking.dto.BookingDtoRequest; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +public class BookingMapper { + + public static BookingDtoResponse toBookingDtoResponse(Booking booking, UserDto userDto, ItemDto itemDto) { + return new BookingDtoResponse( + booking.getId(), + booking.getStart(), + booking.getEnd(), + itemDto, + userDto, + booking.getStatus() + ); + } + + public static Booking toBooking(BookingDtoRequest bookingDtoRequest, User booker, Item item) { + return new Booking( + bookingDtoRequest.getStart(), + bookingDtoRequest.getEnd(), + item, + booker, + bookingDtoRequest.getStatus() + ); + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java new file mode 100644 index 0000000..92a97c0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -0,0 +1,44 @@ +package ru.practicum.shareit.booking.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.booking.enumerated.Status; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Data +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "bookings") +public class Booking { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + @Column(name = "start_date", nullable = false) + private LocalDateTime start; + @Column(name = "end_date", nullable = false) + private LocalDateTime end; + @ManyToOne + @JoinColumn(name = "item_id") + private Item item; + @ManyToOne + @JoinColumn(name = "booker_id") + private User booker; + @Enumerated(EnumType.STRING) + private Status status; + + public Booking(LocalDateTime start, LocalDateTime end, Item item, User booker, Status status) { + this.start = start; + this.end = end; + this.item = item; + this.booker = booker; + this.status = status; + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java new file mode 100644 index 0000000..f6bb1c5 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.booking.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.booking.enumerated.Status; +import ru.practicum.shareit.booking.model.Booking; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public interface BookingRepository extends JpaRepository { + List findAllByBookerIdOrderByStartDesc(Long bookerId); + + Collection findAllByBookerIdAndStartIsBeforeAndEndIsAfterOrderByStartDesc(long bookerId, LocalDateTime startBefore, LocalDateTime endAfter); + + Collection findAllByBookerIdAndEndIsBeforeOrderByStartDesc(long bookerId, LocalDateTime endAfter); + + Collection findAllByBookerIdAndStartIsAfterOrderByStartDesc(long bookerId, LocalDateTime endAfter); + + Collection findAllByBookerIdAndStatusOrderByStartDesc(long bookerId, Status status); + + List findAllByItemIdInOrderByStartDesc(Collection itemId); + + Collection findAllByItemIdInAndStartIsBeforeAndEndIsAfterOrderByStartDesc(Collection itemId, LocalDateTime startBefore, LocalDateTime endAfter); + + Collection findAllByItemIdInAndEndIsBeforeOrderByStartDesc(Collection itemId, LocalDateTime endAfter); + + Collection findAllByItemIdInAndStartIsAfterOrderByStartDesc(Collection itemId, LocalDateTime endAfter); + + Collection findAllByItemIdInAndStatusOrderByStartDesc(Collection itemId, Status status); + + boolean existsBookingByItemIdAndBookerIdAndStatusAndEndIsBefore(Long itemId, long bookerId, Status status, LocalDateTime endBefore); + + Optional findFirstByItemIdAndStartBeforeAndStatusOrderByEndDesc(Long itemId, LocalDateTime startBefore, Status status); + + Optional findFirstByItemIdAndStartAfterAndStatusOrderByStartAsc(Long itemId, LocalDateTime startAfter, Status status); +} diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java new file mode 100644 index 0000000..dfe3ef9 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.booking.service; + +import ru.practicum.shareit.booking.dto.BookingDtoRequest; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; +import ru.practicum.shareit.booking.enumerated.State; + +import java.util.List; + +public interface BookingService { + + BookingDtoResponse getBookingById(Long userId, Long bookingId); + + List getBookingByUser(Long bookerId, State state); + + List getBookingByItemsUser(Long userOwnerItemId, State state); + + BookingDtoResponse createBooking(Long userId, BookingDtoRequest bookingDtoRequest); + + BookingDtoResponse updateBooking(Long userId, Long bookingId, Boolean approved); +} diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java new file mode 100644 index 0000000..bb42749 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -0,0 +1,212 @@ +package ru.practicum.shareit.booking.service; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.dto.BookingDtoRequest; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; +import ru.practicum.shareit.booking.enumerated.State; +import ru.practicum.shareit.booking.enumerated.Status; +import ru.practicum.shareit.booking.mapper.BookingMapper; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.exception.NotAccessException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.dto.ItemByIdDto; +import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.service.ItemService; +import ru.practicum.shareit.user.mapper.UserMapper; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.service.UserService; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +@Slf4j +@Service +@AllArgsConstructor +public class BookingServiceImpl implements BookingService { + private final BookingRepository bookingRepository; + private final UserService userService; + private final ItemService itemService; + + + @Override + public BookingDtoResponse getBookingById(Long userId, Long bookingId) { + log.info("Запрос бронирования с ID = " + bookingId); + Booking booking = getBookingWithCheck(bookingId); + checkBookingUserForGet(userId, booking.getBooker().getId(), booking.getItem().getOwner().getId()); + return BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem())); + } + + @Override + public List getBookingByUser(Long bookerId, State state) { + log.info("Запрос всех бронирования пользователя с ID = " + bookerId); + userService.getUserWithCheck(bookerId); + return switch (state) { + case ALL -> bookingRepository.findAllByBookerIdOrderByStartDesc(bookerId).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case CURRENT -> + bookingRepository.findAllByBookerIdAndStartIsBeforeAndEndIsAfterOrderByStartDesc(bookerId, LocalDateTime.now(), LocalDateTime.now()).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case PAST -> + bookingRepository.findAllByBookerIdAndEndIsBeforeOrderByStartDesc(bookerId, LocalDateTime.now()).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case FUTURE -> + bookingRepository.findAllByBookerIdAndStartIsAfterOrderByStartDesc(bookerId, LocalDateTime.now()).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case WAITING -> + bookingRepository.findAllByBookerIdAndStatusOrderByStartDesc(bookerId, Status.WAITING).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case REJECTED -> + bookingRepository.findAllByBookerIdAndStatusOrderByStartDesc(bookerId, Status.REJECTED).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + default -> throw new ValidationException("Недоступное состояние: " + state); + }; + + } + + @Override + public List getBookingByItemsUser(Long userOwnerItemId, State state) { + log.info("Запрос всех забронированных вещей пользователя с ID = " + userOwnerItemId); + userService.getUserWithCheck(userOwnerItemId); + List itemIds = itemService.getItemsByUserId(userOwnerItemId).stream().map(ItemByIdDto::getId).toList(); + return switch (state) { + case ALL -> bookingRepository.findAllByItemIdInOrderByStartDesc(itemIds).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case CURRENT -> + bookingRepository.findAllByItemIdInAndStartIsBeforeAndEndIsAfterOrderByStartDesc(itemIds, LocalDateTime.now(), LocalDateTime.now()).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case PAST -> + bookingRepository.findAllByItemIdInAndEndIsBeforeOrderByStartDesc(itemIds, LocalDateTime.now()).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case FUTURE -> + bookingRepository.findAllByItemIdInAndStartIsAfterOrderByStartDesc(itemIds, LocalDateTime.now()).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case WAITING -> + bookingRepository.findAllByItemIdInAndStatusOrderByStartDesc(itemIds, Status.WAITING).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + case REJECTED -> + bookingRepository.findAllByItemIdInAndStatusOrderByStartDesc(itemIds, Status.REJECTED).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + default -> throw new ValidationException("Недоступное состояние: " + state); + }; + } + + @Override + public BookingDtoResponse createBooking(Long userId, BookingDtoRequest bookingDtoRequest) { + log.info("Создание бронирования"); + checkDates(bookingDtoRequest.getStart(), bookingDtoRequest.getEnd()); + User user = userService.getUserWithCheck(userId); + Item item = itemService.getItemWithCheck(bookingDtoRequest.getItemId()); + if (!item.getAvailable()) { + throw new ValidationException("Вещь с ID = " + bookingDtoRequest.getItemId() + " забронирована"); + } + bookingDtoRequest.setStatus(Status.WAITING); + return BookingMapper.toBookingDtoResponse( + bookingRepository.save( + BookingMapper.toBooking(bookingDtoRequest, user, item)), + UserMapper.toUserDto(user), + ItemMapper.toItemDto(item)); + } + + @Override + public BookingDtoResponse updateBooking(Long userId, Long bookingId, Boolean approved) { + log.info("Подтверждение бронирования"); + Booking booking = getBookingWithCheck(bookingId); + checkBookingUser(userId, booking.getItem().getOwner().getId()); + if (!booking.getStatus().equals(Status.WAITING)) { + throw new ValidationException("Статус у бронирования должен быть \"В ожидании подтверждения\""); + } + userService.getUserWithCheck(userId); + if (approved) { + booking.setStatus(Status.APPROVED); + } else { + booking.setStatus(Status.REJECTED); + } + return BookingMapper.toBookingDtoResponse( + bookingRepository.save(booking), + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem())); + } + + private Booking getBookingWithCheck(Long bookingId) { + return bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Бронирование с ID = " + bookingId + " не найдено")); + } + + private void checkDates(LocalDateTime start, LocalDateTime end) { + if (start.isEqual(end)) { + throw new ValidationException("Дата начала и дата окончания бронирования не могут быть равны"); + } + if (start.isAfter(end)) { + throw new ValidationException("Дата начала бронирования не может быть позже даты окончания бронирования"); + } + } + + private void checkBookingUser(Long userId, Long ownerItemId) { + if (!Objects.equals(userId, ownerItemId)) { + throw new NotAccessException("У данного пользователя нет прав на данное действие"); + } + } + + private void checkBookingUserForGet(Long userId, Long bookerId, Long ownerItemId) { + if (!Objects.equals(userId, bookerId) && !Objects.equals(userId, ownerItemId)) { + throw new NotAccessException("У данного пользователя нет прав на данное действие"); + } + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java index 000fbff..00356ce 100644 --- a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java +++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -38,4 +38,11 @@ public ErrorResponse handleValidationException(NotAccessException e) { log.debug(e.getMessage(), e); return new ErrorResponse(HttpStatus.FORBIDDEN.value(), e.getMessage()); } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleValidationException(ValidationException e) { + log.debug(e.getMessage(), e); + return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage()); + } } 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..59043da --- /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 af398c8..1176090 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -3,6 +3,8 @@ import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemByIdDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.service.ItemService; @@ -14,21 +16,22 @@ public class ItemController { private final ItemService itemService; + private final String userIdHeader = "X-Sharer-User-Id"; @GetMapping - public List getItemsByUserId(@RequestHeader("X-Sharer-User-Id") Long userId) { + public List getItemsByUserId(@RequestHeader(userIdHeader) Long userId) { return itemService.getItemsByUserId(userId); } @GetMapping("/{itemId}") - public ItemDto getItemById(@PathVariable Long itemId) { - return itemService.getItemById(itemId); + public ItemByIdDto getItemById(@PathVariable Long itemId, @RequestHeader(userIdHeader) Long userId) { + return itemService.getItemById(itemId, userId); } @PostMapping public ItemDto createItem( @Valid @RequestBody ItemDto itemDto, - @RequestHeader("X-Sharer-User-Id") Long userId) { + @RequestHeader(userIdHeader) Long userId) { return itemService.createItem(itemDto, userId); } @@ -36,7 +39,7 @@ public ItemDto createItem( public ItemDto updateItem( @RequestBody ItemDto itemDto, @PathVariable Long itemId, - @RequestHeader("X-Sharer-User-Id") Long userId) { + @RequestHeader(userIdHeader) Long userId) { return itemService.updateItem(itemDto, itemId, userId); } @@ -44,4 +47,12 @@ public ItemDto updateItem( public List getItemsSearchByText(@RequestParam String text) { return itemService.searchItemsByText(text); } + + @PostMapping("/{itemId}/comment") + public CommentDto createCommet( + @Valid @RequestBody CommentDto commentDto, + @PathVariable Long itemId, + @RequestHeader(userIdHeader) Long userId) { + return itemService.createComment(itemId, userId, commentDto); + } } diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..77adf45 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class CommentDto { + private Long id; + @NotBlank(message = "Текст отзыва не может быть пустым") + private String text; + private String authorName; + private LocalDateTime created; +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemByIdDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemByIdDto.java new file mode 100644 index 0000000..1f075c3 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemByIdDto.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemByIdDto { + private Long id; + private String name; + private String description; + private Boolean available; + private BookingDtoResponse lastBooking; + private BookingDtoResponse nextBooking; + private List comments; +} 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 1b82a99..442e215 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -4,9 +4,13 @@ import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; @Data @AllArgsConstructor +@NoArgsConstructor public class ItemDto { private Long id; @NotBlank(message = "Название не может быть пустым") @@ -15,5 +19,7 @@ public class ItemDto { private String description; @NotNull(message = "Статус о доступности не может быть пустым") private Boolean available; - private Long requestId; + + private List comments; + } 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 76264bf..4d51f03 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -1,10 +1,16 @@ package ru.practicum.shareit.item.mapper; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemByIdDto; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Comment; import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.request.ItemRequest; import ru.practicum.shareit.user.model.User; +import java.time.LocalDateTime; +import java.util.ArrayList; + public class ItemMapper { public static ItemDto toItemDto(Item item) { @@ -13,17 +19,51 @@ public static ItemDto toItemDto(Item item) { item.getName(), item.getDescription(), item.getAvailable(), - item.getRequest() != null ? item.getRequest().getId() : null + item.getComments() == null ? new ArrayList<>() : item.getComments().stream() + .map(ItemMapper::toCommentDto) + .toList() ); } - public static Item toItem(ItemDto itemDto, User owner, ItemRequest request) { + public static Item toItem(ItemDto itemDto, User owner) { return new Item( itemDto.getName(), itemDto.getDescription(), itemDto.getAvailable(), - owner, - request + owner + ); + } + + public static ItemByIdDto toItemByIdDto(Item item, BookingDtoResponse nextBooking, BookingDtoResponse lastBooking) { + return new ItemByIdDto( + item.getId(), + item.getName(), + item.getDescription(), + item.getAvailable(), + lastBooking, + nextBooking, + item.getComments() == null ? new ArrayList<>() : item.getComments().stream() + .map(ItemMapper::toCommentDto) + .toList() + ); + } + + public static CommentDto toCommentDto(Comment comment) { + return new CommentDto( + comment.getId(), + comment.getText(), + comment.getAuthor().getName(), + comment.getCreated() + ); + } + + public static Comment toComment(CommentDto commentDto, User author, Item item) { + return new Comment( + commentDto.getId(), + commentDto.getText(), + item, + author, + LocalDateTime.now() ); } diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/src/main/java/ru/practicum/shareit/item/model/Comment.java new file mode 100644 index 0000000..c83a675 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/model/Comment.java @@ -0,0 +1,31 @@ +package ru.practicum.shareit.item.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Data +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "comments") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + @Column(nullable = false, length = 500) + private String text; + @ManyToOne + @JoinColumn(name = "item_id") + private Item item; + @ManyToOne + @JoinColumn(name = "author_id") + private User author; + @Column + private LocalDateTime created; +} 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 69e581b..9a99d15 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,26 +1,46 @@ package ru.practicum.shareit.item.model; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Data; -import ru.practicum.shareit.request.ItemRequest; +import lombok.NoArgsConstructor; import ru.practicum.shareit.user.model.User; +import java.util.List; + @Data +@Entity +@NoArgsConstructor @AllArgsConstructor +@Table(name = "items") public class Item { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column private Long id; + + @Column(nullable = false) private String name; + + @Column(nullable = false, length = 500) private String description; + + @Column private Boolean available; + + @ManyToOne + @JoinColumn(name = "owner_id") private User owner; - private ItemRequest request; - public Item(String name, String description, Boolean available, User owner, ItemRequest request) { + @OneToMany(mappedBy = "item") + private List comments; + + public Item(String name, String description, Boolean available, User owner) { this.name = name; this.description = description; this.available = available; this.owner = owner; - this.request = request; } } diff --git a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java new file mode 100644 index 0000000..63277ce --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.item.model.Comment; + +public interface CommentRepository extends JpaRepository { +} diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java index 73c780d..b725926 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -1,20 +1,22 @@ package ru.practicum.shareit.item.repository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; import java.util.List; -import java.util.Optional; -public interface ItemRepository { - List getItemsByUserId(Long userId); +public interface ItemRepository extends JpaRepository { - Item createItem(Item item); - - Item updateItem(Item item); - - Optional getItemById(Long itemId); - - List searchItemsByText(String text); + List findAllByOwner(User owner); + @Query( + "SELECT item " + + "FROM Item item " + + "WHERE (item.name ilike :text " + + "OR item.description ilike :text) " + + "AND item.available = true") + List findAllByTextIgnoreCaseAndAvailableIsTrue(String text); } diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java deleted file mode 100644 index 297c005..0000000 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.practicum.shareit.item.repository; - -import org.springframework.stereotype.Repository; -import ru.practicum.shareit.item.model.Item; - -import java.util.*; - -@Repository -public class ItemRepositoryImpl implements ItemRepository { - - private final Map items = new HashMap<>(); - - @Override - public List getItemsByUserId(Long userId) { - return items.values().stream() - .filter(item -> Objects.equals(item.getOwner().getId(), userId)) - .toList(); - } - - @Override - public Item createItem(Item item) { - item.setId(getNewItemId()); - items.put(item.getId(), item); - return items.get(item.getId()); - } - - @Override - public Item updateItem(Item item) { - items.put(item.getId(), item); - return items.get(item.getId()); - } - - @Override - public Optional getItemById(Long itemId) { - return Optional.ofNullable(items.get(itemId)); - } - - @Override - public List searchItemsByText(String text) { - return items.values().stream() - .filter(item -> (containsIgnoreCase(item.getName(), text) || - containsIgnoreCase(item.getDescription(), text)) && item.getAvailable()) - .toList(); - } - - private Boolean containsIgnoreCase(String text, String containsText) { - return text.toLowerCase().contains(containsText.toLowerCase()); - } - - private Long getNewItemId() { - return items.keySet().stream() - .max(Long::compareTo) - .orElse(0L) + 1; - } -} diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java index f76170f..f775db4 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -1,17 +1,24 @@ package ru.practicum.shareit.item.service; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemByIdDto; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; import java.util.List; public interface ItemService { - List getItemsByUserId(Long userId); + List getItemsByUserId(Long userId); + + Item getItemWithCheck(Long itemId); ItemDto createItem(ItemDto itemDto, Long userId); ItemDto updateItem(ItemDto itemDto, Long itemId, Long userId); - ItemDto getItemById(Long itemId); + ItemByIdDto getItemById(Long itemId, Long userId); List searchItemsByText(String text); + + CommentDto createComment(Long itemId, Long userId, CommentDto commentDto); } 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 95ddca5..bbdd0c6 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -3,18 +3,28 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.dto.BookingDtoResponse; +import ru.practicum.shareit.booking.enumerated.Status; +import ru.practicum.shareit.booking.mapper.BookingMapper; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.exception.NotAccessException; import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemByIdDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.item.model.Comment; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.CommentRepository; import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.mapper.UserMapper; import ru.practicum.shareit.user.model.User; import ru.practicum.shareit.user.service.UserService; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.time.LocalDateTime; +import java.util.*; @Slf4j @Service @@ -23,20 +33,38 @@ public class ItemServiceImpl implements ItemService { private final ItemRepository itemRepository; private final UserService userService; + private final BookingRepository bookingRepository; + private final CommentRepository commentRepository; @Override - public List getItemsByUserId(Long userId) { + public List getItemsByUserId(Long userId) { log.info("Поиск вещей пользователя с ID = {}", userId); - userService.getUserWithCheck(userId); - List itemsByUserId = itemRepository.getItemsByUserId(userId); + User user = userService.getUserWithCheck(userId); + List itemsByUserId = itemRepository.findAllByOwner(user); + Map> bookingsByIdItem = getMapItemIdAndListBooking( + itemsByUserId.stream() + .map(Item::getId) + .toList()); return itemsByUserId.stream() - .map(ItemMapper::toItemDto) + .map(item -> { + List bookings = bookingsByIdItem.getOrDefault(item.getId(), new ArrayList<>()); + BookingDtoResponse lastBooking = bookings.stream() + .filter(booking -> booking.getStatus().equals(Status.APPROVED)) + .max(Comparator.comparing(BookingDtoResponse::getEnd)) + .orElse(null); + BookingDtoResponse nextBooking = bookings.stream() + .filter(booking -> booking.getStatus().equals(Status.APPROVED)) + .min(Comparator.comparing(BookingDtoResponse::getStart)) + .orElse(null); + return ItemMapper.toItemByIdDto(item, nextBooking, lastBooking); + }) .toList(); } + @Override public Item getItemWithCheck(Long itemId) { - return itemRepository.getItemById(itemId) + return itemRepository.findById(itemId) .orElseThrow(() -> new NotFoundException("Вещь с ID = " + itemId + " не найдена")); } @@ -45,8 +73,8 @@ public ItemDto createItem(ItemDto itemDto, Long userId) { log.info("Создание вещи у пользователя с ID = {}", userId); User user = userService.getUserWithCheck(userId); return ItemMapper.toItemDto( - itemRepository.createItem( - ItemMapper.toItem(itemDto, user, null))); + itemRepository.save( + ItemMapper.toItem(itemDto, user))); } @Override @@ -63,14 +91,45 @@ public ItemDto updateItem(ItemDto itemDto, Long itemId, Long userId) { findItem.getDescription() : itemDto.getDescription()); findItem.setAvailable(itemDto.getAvailable() != null ? itemDto.getAvailable() : findItem.getAvailable()); - return ItemMapper.toItemDto(itemRepository.updateItem(findItem)); + return ItemMapper.toItemDto(itemRepository.save(findItem)); } @Override - public ItemDto getItemById(Long itemId) { + public ItemByIdDto getItemById(Long itemId, Long userId) { log.info("Запрос вещи с ID = {}", itemId); Item item = getItemWithCheck(itemId); - return ItemMapper.toItemDto(item); + User user = userService.getUserWithCheck(userId); + BookingDtoResponse lastBooking = null; + BookingDtoResponse nextBooking = null; + if (item.getOwner().getId().equals(userId)) { + lastBooking = getLastBookingDtoResponse(itemId, lastBooking, user, item); + nextBooking = getNextBookingDtoResponse(itemId, nextBooking, user, item); + } + return ItemMapper.toItemByIdDto(item, nextBooking, lastBooking); + } + + private BookingDtoResponse getNextBookingDtoResponse(Long itemId, BookingDtoResponse nextBooking, User user, Item item) { + Optional nextBookingOptional = bookingRepository.findFirstByItemIdAndStartAfterAndStatusOrderByStartAsc( + itemId, LocalDateTime.now(), Status.APPROVED); + if (nextBookingOptional.isPresent()) { + nextBooking = BookingMapper.toBookingDtoResponse( + nextBookingOptional.get(), + UserMapper.toUserDto(user), + ItemMapper.toItemDto(item)); + } + return nextBooking; + } + + private BookingDtoResponse getLastBookingDtoResponse(Long itemId, BookingDtoResponse lastBooking, User user, Item item) { + Optional lastBookingOptional = bookingRepository.findFirstByItemIdAndStartBeforeAndStatusOrderByEndDesc( + itemId, LocalDateTime.now(), Status.APPROVED); + if (lastBookingOptional.isPresent()) { + lastBooking = BookingMapper.toBookingDtoResponse( + lastBookingOptional.get(), + UserMapper.toUserDto(user), + ItemMapper.toItemDto(item)); + } + return lastBooking; } @Override @@ -79,15 +138,45 @@ public List searchItemsByText(String text) { if (text.isBlank()) { return new ArrayList<>(); } - return itemRepository.searchItemsByText(text).stream() + return itemRepository.findAllByTextIgnoreCaseAndAvailableIsTrue(text).stream() .map(ItemMapper::toItemDto) .toList(); } + @Override + public CommentDto createComment(Long itemId, Long userId, CommentDto commentDto) { + if (!bookingRepository.existsBookingByItemIdAndBookerIdAndStatusAndEndIsBefore(itemId, userId, + Status.APPROVED, LocalDateTime.now())) { + throw new ValidationException("У пользователя с ID = " + userId + " не было ни одной брони на вещь с ID = " + itemId); + } + User author = userService.getUserWithCheck(userId); + Item item = getItemWithCheck(itemId); + Comment comment = ItemMapper.toComment(commentDto, author, item); + return ItemMapper.toCommentDto(commentRepository.save(comment)); + } + private void checkUser(Item item, Long userIdFromRequest) { if (!Objects.equals(item.getOwner().getId(), userIdFromRequest)) { throw new NotAccessException("Вещь с ID = " + item.getId() + " не принадлежит пользователю с ID = " + userIdFromRequest); } } + private Map> getMapItemIdAndListBooking(List itemIds) { + Map> map = new HashMap<>(); + List bookings = bookingRepository.findAllByItemIdInOrderByStartDesc(itemIds).stream() + .map(booking -> BookingMapper.toBookingDtoResponse( + booking, + UserMapper.toUserDto(booking.getBooker()), + ItemMapper.toItemDto(booking.getItem()))) + .toList(); + bookings.forEach(booking -> { + if (map.containsKey(booking.getItem().getId())) { + map.get(booking.getItem().getId()).add(booking); + } else { + map.put(booking.getItem().getId(), new ArrayList<>(Arrays.asList(booking))); + } + }); + return map; + } + } diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java index c3779e3..794b025 100644 --- a/src/main/java/ru/practicum/shareit/user/model/User.java +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -1,15 +1,27 @@ package ru.practicum.shareit.user.model; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@Entity +@NoArgsConstructor @AllArgsConstructor +@Table(name = "users") public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String name; + + @Column private String email; + @Column + private String name; + public User(String name, String email) { this.name = name; this.email = email; diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java index 820e66f..9218c89 100644 --- a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -1,21 +1,9 @@ package ru.practicum.shareit.user.repository; +import org.springframework.data.jpa.repository.JpaRepository; import ru.practicum.shareit.user.model.User; -import java.util.List; -import java.util.Optional; +public interface UserRepository extends JpaRepository { -public interface UserRepository { - - List getUsers(); - - Optional getUserById(Long userId); - - User createUser(User user); - - User updateUser(User user); - - void deleteUserById(Long userId); - - void checkEmail(String email); + boolean existsUserByEmail(String email); } diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java deleted file mode 100644 index d52efba..0000000 --- a/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.practicum.shareit.user.repository; - -import org.springframework.stereotype.Repository; -import ru.practicum.shareit.exception.AlreadyExistException; -import ru.practicum.shareit.user.model.User; - -import java.util.*; - -@Repository -public class UserRepositoryImpl implements UserRepository { - - private final Map users = new HashMap<>(); - - @Override - public List getUsers() { - return new ArrayList<>(users.values()); - } - - @Override - public Optional getUserById(Long userId) { - return Optional.ofNullable(users.get(userId)); - } - - @Override - public User createUser(User user) { - user.setId(getNewUserId()); - users.put(user.getId(), user); - return users.get(user.getId()); - } - - @Override - public User updateUser(User user) { - users.put(user.getId(), user); - return users.get(user.getId()); - } - - @Override - public void deleteUserById(Long userId) { - users.remove(userId); - } - - @Override - public void checkEmail(String email) { - if (getUsers().stream() - .map(User::getEmail) - .anyMatch(email::equals)) { - throw new AlreadyExistException("Пользователь с почтой " + email + " уже существует"); - } - } - - private Long getNewUserId() { - return users.keySet().stream() - .max(Long::compareTo) - .orElse(0L) + 1; - } -} 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 8cc1711..fe2dced 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.AlreadyExistException; import ru.practicum.shareit.exception.NotFoundException; import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.user.mapper.UserMapper; @@ -25,16 +26,16 @@ public UserDto getUserById(Long userId) { @Override public User getUserWithCheck(Long userId) { - return userRepository.getUserById(userId) + return userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("Пользователь с ID = " + userId + " не найден")); } @Override public UserDto createUser(UserDto userDto) { log.info("Создание пользователя"); - userRepository.checkEmail(userDto.getEmail()); + checkEmail(userDto.getEmail()); return UserMapper.toUserDto( - userRepository.createUser( + userRepository.save( UserMapper.toUser(userDto))); } @@ -43,19 +44,25 @@ public UserDto updateUser(UserDto userDto, Long userId) { log.info("Обновление пользователя с ID = {}", userId); User findUser = getUserWithCheck(userId); if (userDto.getEmail() != null && !userDto.getEmail().equals(findUser.getEmail())) { - userRepository.checkEmail(userDto.getEmail()); + checkEmail(userDto.getEmail()); } findUser.setEmail(userDto.getEmail() == null || userDto.getEmail().isBlank() ? findUser.getEmail() : userDto.getEmail()); findUser.setName(userDto.getName() == null || userDto.getName().isBlank() ? findUser.getName() : userDto.getName()); - return UserMapper.toUserDto(userRepository.updateUser(findUser)); + return UserMapper.toUserDto(userRepository.save(findUser)); } @Override public void deleteUserById(Long userId) { log.info("Удаление пользователя с ID = {}", userId); getUserWithCheck(userId); - userRepository.deleteUserById(userId); + userRepository.deleteById(userId); + } + + private void checkEmail(String email) { + if (userRepository.existsUserByEmail(email)) { + throw new AlreadyExistException("Пользователь с почтой " + email + " уже существует"); + } } } diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..61f0636 --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,7 @@ +spring.jpa.show-sql=true + +spring.datasource.url=jdbc:h2:file:./db/shareIt +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +logging.level.org.zalando.logbook: TRACE \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 9e9bc4b..2bc5896 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -6,8 +6,7 @@ logging.level.org.springframework.transaction=INFO logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG -# TODO Append connection to H2 DB -#spring.datasource.driverClassName -#spring.datasource.url -#spring.datasource.username -#spring.datasource.password +spring.datasource.url=jdbc:h2:file:./db/shareIt +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 51c5180..90b8b74 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,8 +7,8 @@ logging.level.org.springframework.transaction=INFO logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG -# TODO Append connection to Postgres DB -#spring.datasource.driverClassName -#spring.datasource.url -#spring.datasource.username -#spring.datasource.password +spring.datasource.driverClassName=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:5432/shareIt +spring.datasource.username=root +spring.datasource.password=root + diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..b2d46a3 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,37 @@ +DROP TABLE IF EXISTS comments, bookings, items, users CASCADE ; + +CREATE TABLE IF NOT EXISTS users +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + CONSTRAINT UQ_USER_EMAIL UNIQUE (email) + ); + +CREATE TABLE IF NOT EXISTS items +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description VARCHAR(500) NOT NULL, + available boolean default true, + owner_id BIGINT REFERENCES users (id) ON DELETE RESTRICT + ); + +CREATE TABLE IF NOT EXISTS bookings +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + start_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + end_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + item_id BIGINT REFERENCES items (id) ON DELETE RESTRICT, + booker_id BIGINT REFERENCES users (id)ON DELETE RESTRICT, + status VARCHAR + ); + +CREATE TABLE IF NOT EXISTS comments +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + text VARCHAR (500) NOT NULL, + item_id BIGINT REFERENCES items (id) ON DELETE RESTRICT, + author_id BIGINT REFERENCES users (id) ON DELETE RESTRICT, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL + ); \ No newline at end of file