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