diff --git a/pom.xml b/pom.xml
index 870ae8526..0efdef4b2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,241 +1,260 @@
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.7.2
-
-
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.2
+
+
- ru.practicum
- shareit
- 0.0.1-SNAPSHOT
+ ru.practicum
+ shareit
+ 0.0.1-SNAPSHOT
- ShareIt
+ ShareIt
-
- 11
-
+
+ 11
+
-
-
- org.springframework.boot
- spring-boot-starter-web
-
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
-
- org.postgresql
- postgresql
- runtime
-
-
- com.h2database
- h2
- runtime
-
-
- org.springframework.boot
- spring-boot-configuration-processor
- true
-
-
- org.projectlombok
- lombok
- true
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 6.0.10.Final
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter
+
-
-
-
- src/main/resources
- true
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- test
-
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- 3.1.2
-
- checkstyle.xml
- true
- true
- true
-
-
-
-
- check
-
- compile
-
-
-
-
- com.puppycrawl.tools
- checkstyle
- 10.3
-
-
-
-
- com.github.spotbugs
- spotbugs-maven-plugin
- 4.7.0.0
-
- Max
- High
-
-
-
-
- check
-
-
-
-
-
- org.jacoco
- jacoco-maven-plugin
- 0.8.8
-
-
-
-
-
- jacoco-initialize
-
- prepare-agent
-
-
-
- jacoco-check
-
- check
-
-
-
-
- BUNDLE
-
-
- INSTRUCTION
- COVEREDRATIO
- 0.01
-
-
- LINE
- COVEREDRATIO
- 0.9
-
-
- BRANCH
- COVEREDRATIO
- 0.6
-
-
- COMPLEXITY
- COVEREDRATIO
- 0.6
-
-
- METHOD
- COVEREDRATIO
- 0.7
-
-
- CLASS
- MISSEDCOUNT
- 1
-
-
-
-
-
-
-
- jacoco-report
- test
-
- report
-
-
-
-
-
-
-
-
-
- check
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
-
-
- com.github.spotbugs
- spotbugs-maven-plugin
-
-
-
-
-
-
- com.github.spotbugs
- spotbugs-maven-plugin
-
-
-
-
-
- coverage
-
-
-
- org.jacoco
- jacoco-maven-plugin
-
-
-
-
-
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ test
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.1.2
+
+ checkstyle.xml
+ true
+ true
+ true
+
+
+
+
+ check
+
+ compile
+
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 10.3
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.7.0.0
+
+ Max
+ High
+
+
+
+
+ check
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.8
+
+
+
+
+
+ jacoco-initialize
+
+ prepare-agent
+
+
+
+ jacoco-check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.01
+
+
+ LINE
+ COVEREDRATIO
+ 0.9
+
+
+ BRANCH
+ COVEREDRATIO
+ 0.6
+
+
+ COMPLEXITY
+ COVEREDRATIO
+ 0.6
+
+
+ METHOD
+ COVEREDRATIO
+ 0.7
+
+
+ CLASS
+ MISSEDCOUNT
+ 1
+
+
+
+
+
+
+
+ jacoco-report
+ test
+
+ report
+
+
+
+
+
+
+
+
+
+ check
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+
+
+
+
+
+ coverage
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
+
+
diff --git a/src/main/java/ru/practicum/shareit/Create.java b/src/main/java/ru/practicum/shareit/Create.java
new file mode 100644
index 000000000..5b84b9da0
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/Create.java
@@ -0,0 +1,4 @@
+package ru.practicum.shareit;
+
+public interface Create {
+}
diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java
index a00ad567d..b69dbb5a6 100644
--- a/src/main/java/ru/practicum/shareit/ShareItApp.java
+++ b/src/main/java/ru/practicum/shareit/ShareItApp.java
@@ -7,6 +7,7 @@
public class ShareItApp {
public static void main(String[] args) {
+
SpringApplication.run(ShareItApp.class, args);
}
diff --git a/src/main/java/ru/practicum/shareit/Update.java b/src/main/java/ru/practicum/shareit/Update.java
new file mode 100644
index 000000000..067ab2926
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/Update.java
@@ -0,0 +1,4 @@
+package ru.practicum.shareit;
+
+public interface Update {
+}
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 2d9c6668d..000000000
--- a/src/main/java/ru/practicum/shareit/booking/Booking.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package ru.practicum.shareit.booking;
-
-/**
- * TODO Sprint add-bookings.
- */
-public class Booking {
-}
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java
index b94493d49..9bd9026a4 100644
--- a/src/main/java/ru/practicum/shareit/booking/BookingController.java
+++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java
@@ -1,12 +1,68 @@
package ru.practicum.shareit.booking;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.booking.dto.BookingDTO;
+import ru.practicum.shareit.booking.dto.BookingDTOToReturn;
+import ru.practicum.shareit.booking.service.BookingService;
+
+import javax.validation.Valid;
+import javax.validation.constraints.PositiveOrZero;
+import java.util.List;
-/**
- * TODO Sprint add-bookings.
- */
@RestController
@RequestMapping(path = "/bookings")
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Slf4j
+@Validated
public class BookingController {
+ private final BookingService bookingService;
+
+ @PostMapping
+ public BookingDTOToReturn add(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @Valid @RequestBody BookingDTO bookingDto) {
+ log.info("Добавление запроса на аренду пользователем с id {}", userId);
+ return bookingService.add(userId, bookingDto);
+ }
+
+ @PatchMapping("/{bookingId}")
+ public BookingDTOToReturn update(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable Long bookingId,
+ @RequestParam Boolean approved) {
+ log.info("Обновление статуса запроса на аренду с id {}", bookingId);
+ return bookingService.update(userId, bookingId, approved);
+
+ }
+
+ @GetMapping("/{bookingId}")
+ public BookingDTOToReturn get(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long bookingId) {
+ log.info("Просмотр запроса на пренду с id {}", bookingId);
+ return bookingService.get(userId, bookingId);
+ }
+
+ @GetMapping
+ public List findByBooker(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestParam(required = false, defaultValue = "ALL") String state,
+ @PositiveOrZero @RequestParam(name = "from", defaultValue = "0")
+ Integer from,
+ @PositiveOrZero @RequestParam(name = "size", defaultValue = "10")
+ Integer size) {
+ log.info("Получение списка бронирований пользовалеля с id {}", userId);
+ return bookingService.getByBooker(userId, state, from, size);
+ }
+
+ @GetMapping("/owner")
+ public List findByOwner(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestParam(defaultValue = "ALL") String state,
+ @PositiveOrZero @RequestParam(name = "from", defaultValue = "0")
+ Integer from,
+ @PositiveOrZero @RequestParam(name = "size", defaultValue = "10")
+ Integer size) {
+ log.info("Получение списка бронирований для всех вещей пользователя с id {}", userId);
+ return bookingService.getByOwner(userId, state, from, size);
+ }
+
}
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java
new file mode 100644
index 000000000..37f79af8d
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java
@@ -0,0 +1,69 @@
+package ru.practicum.shareit.booking;
+
+import lombok.experimental.UtilityClass;
+import ru.practicum.shareit.booking.dto.BookingDTO;
+import ru.practicum.shareit.booking.dto.BookingDTOForItem;
+import ru.practicum.shareit.booking.dto.BookingDTOToReturn;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.model.Status;
+import ru.practicum.shareit.item.mapper.ItemMapper;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.UserMapper;
+import ru.practicum.shareit.user.model.User;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@UtilityClass
+public class BookingMapper {
+
+ public static BookingDTOToReturn toBookingDtoFrom(Booking booking) {
+ BookingDTOToReturn bookingDto = new BookingDTOToReturn();
+ bookingDto.setId(booking.getId());
+ bookingDto.setStart(booking.getStart());
+ bookingDto.setEnd(booking.getEnd());
+ bookingDto.setStatus(booking.getStatus());
+ bookingDto.setItem(ItemMapper.toItemToBookingDTO(booking.getItem()));
+ bookingDto.setBooker(UserMapper.toUserToBookingDTO(booking.getBooker()));
+ return bookingDto;
+ }
+
+ public static Booking toBooking(BookingDTO bookingDto, Item item, User user) {
+ Booking booking = new Booking();
+ booking.setStart(bookingDto.getStart());
+ booking.setEnd(bookingDto.getEnd());
+ booking.setId(bookingDto.getId());
+ booking.setStatus(Status.WAITING);
+ booking.setItem(item);
+ booking.setBooker(user);
+ return booking;
+ }
+
+ public static BookingDTOForItem toBookingDtoForItem(long id, long bookerId) {
+ BookingDTOForItem bookingDtoForItem = new BookingDTOForItem();
+ bookingDtoForItem.setId(id);
+ bookingDtoForItem.setBookerId(bookerId);
+ return bookingDtoForItem;
+ }
+
+
+ public static List mapToBookingDtoFrom(Iterable bookings) {
+ List dtos = new ArrayList<>();
+ for (Booking booking : bookings) {
+ dtos.add(toBookingDtoFrom(booking));
+ }
+ return dtos;
+ }
+
+ public static BookingDTO toBookingDto(Booking booking) {
+ BookingDTO bookingDto = new BookingDTO();
+ bookingDto.setId(booking.getId());
+ bookingDto.setStart(booking.getStart());
+ bookingDto.setEnd(booking.getEnd());
+ bookingDto.setStatus(booking.getStatus());
+ bookingDto.setItemId(booking.getItem() != null ? booking.getItem().getId() : null);
+ bookingDto.setItemName(booking.getItem() != null ? booking.getItem().getName() : null);
+ bookingDto.setBookerId(booking.getBooker() != null ? booking.getBooker().getId() : null);
+ return bookingDto;
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/BookingRepository.java
new file mode 100644
index 000000000..7dce6502c
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/BookingRepository.java
@@ -0,0 +1,107 @@
+package ru.practicum.shareit.booking;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.model.Status;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+public interface BookingRepository extends JpaRepository {
+
+ List findByBookerAndStatusOrderByStartDesc(User booker, Status status);
+
+ List findByBookerOrderByStartDesc(User booker);
+
+ Page findByBookerAndStatusOrderByStartDesc(User booker, Status status, Pageable pageable);
+
+ Page findByBookerOrderByStartDesc(User booker, Pageable pageable);
+
+ List findByBookerAndStartAfterOrderByStartDesc(User booker, LocalDateTime now);
+
+ Page findByBookerAndStartAfterOrderByStartDesc(User booker, LocalDateTime now, Pageable pageable);
+
+ List findByBookerAndStartBeforeAndEndAfterOrderByStartDesc(User booker, LocalDateTime s, LocalDateTime e);
+
+ Page findByBookerAndStartBeforeAndEndAfterOrderByStartDesc(User booker, LocalDateTime s, LocalDateTime e,
+ Pageable pageable);
+
+ List findByBookerAndStartBeforeAndEndBeforeOrderByStartDesc(User booker, LocalDateTime s, LocalDateTime e);
+
+ Page findByBookerAndStartBeforeAndEndBeforeOrderByStartDesc(User booker, LocalDateTime s,
+ LocalDateTime e, Pageable pageable);
+
+ List findByItemAndBookerAndStartBeforeAndEndBefore(Item item, User booker, LocalDateTime s,
+ LocalDateTime e);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id IN" + " (SELECT i.id FROM Item i WHERE i.owner.id = ?1)"
+ + " ORDER BY b.id DESC")
+ List findByOwnerAll(long userId);
+
+ @Query(nativeQuery = true, value = "SELECT * FROM BOOKINGS as b " +
+ "LEFT JOIN ITEMS as i ON b.ITEM_ID = i.ID " +
+ "WHERE i.OWNER_ID = ?1 " +
+ "ORDER BY b.START_DATE DESC")
+ List findByOwnerAll(Long i, Pageable pageable);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id IN "
+ + "(SELECT i.id FROM Item i WHERE i.owner.id = ?1) AND b.start < ?2 AND b.end > ?2"
+ + " ORDER BY b.id DESC")
+ List findByOwnerAndCurrent(long userId, LocalDateTime currentDate);
+
+ @Query(nativeQuery = true, value = "SELECT * FROM BOOKINGS as b " +
+ "LEFT JOIN ITEMS as i ON b.ITEM_ID = i.ID " +
+ "WHERE i.OWNER_ID = ?1 AND b.START_DATE < ?2 AND b.END_DATE > ?2 " +
+ "ORDER BY b.START_DATE DESC")
+ List findByOwnerAndCurrent(Long i, LocalDateTime now, Pageable pageable);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id IN "
+ + "(SELECT i.id FROM Item i WHERE i.owner.id = ?1) AND b.end < ?2"
+ + " ORDER BY b.id DESC")
+ List findByOwnerAndPast(long userId, LocalDateTime currentDate);
+
+ @Query(nativeQuery = true, value = "SELECT * FROM BOOKINGS as b " +
+ "LEFT JOIN ITEMS as i ON b.ITEM_ID = i.ID " +
+ "WHERE i.OWNER_ID = ?1 AND b.END_DATE < ?2 AND b.START_DATE < ?2 " +
+ "ORDER BY b.START_DATE DESC")
+ List findByOwnerAndPast(Long i, LocalDateTime now, Pageable pageable);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id IN "
+ + "(SELECT i.id FROM Item i WHERE i.owner.id = ?1) AND b.start > ?2" + " ORDER BY b.id DESC")
+ List findByUserAndFuture(long userId, LocalDateTime currentDate);
+
+ @Query(nativeQuery = true, value = "SELECT * FROM BOOKINGS as b " +
+ "LEFT JOIN ITEMS as i ON b.ITEM_ID = i.ID " +
+ "WHERE i.OWNER_ID = ?1 AND b.START_DATE > ?2 " +
+ "ORDER BY b.START_DATE DESC")
+ List findByUserAndFuture(Long i, LocalDateTime s, Pageable pageable);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id IN "
+ + "(SELECT i.id FROM Item i WHERE i.owner.id = ?1) AND b.status = ?2" + " ORDER BY b.id DESC")
+ List findByOwnerAndByStatus(long userId, Status status);
+
+ @Query(nativeQuery = true, value = "SELECT * FROM BOOKINGS as b " +
+ "LEFT JOIN ITEMS as i ON b.ITEM_ID = i.ID " +
+ "WHERE i.OWNER_ID = ?1 AND b.STATUS LIKE ?2 " +
+ "ORDER BY b.START_DATE DESC")
+ List findByOwnerAndByStatus(Long i, String status, Pageable pageable);
+
+ Booking findFirstByStatusAndItemAndStartIsAfter(Status state, Item item, LocalDateTime time, Sort sort);
+
+ Booking findFirstByStatusAndItemAndStartLessThanEqual(Status state, Item item, LocalDateTime time, Sort sort);
+
+ @Query("select b " +
+ "from Booking b " +
+ "where b.item in ?1 " +
+ " and b.status = 'APPROVED'")
+ List findApprovedForItems(Collection- items, Sort sort);
+
+ List findByItemOrderByStartDesc(Item item);
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDTO.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTO.java
new file mode 100644
index 000000000..2114dd883
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTO.java
@@ -0,0 +1,29 @@
+package ru.practicum.shareit.booking.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.shareit.booking.model.Status;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+public class BookingDTO {
+
+ private long id;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ private LocalDateTime start;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ private LocalDateTime end;
+
+ private Long itemId;
+
+ private String itemName;
+
+ private Long bookerId;
+
+ private Status status;
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDTOForItem.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTOForItem.java
new file mode 100644
index 000000000..eb921a5a7
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTOForItem.java
@@ -0,0 +1,20 @@
+package ru.practicum.shareit.booking.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@NoArgsConstructor
+public class BookingDTOForItem {
+ private long id;
+ private Long bookerId;
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ private LocalDateTime dateTime;
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDTOToReturn.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTOToReturn.java
new file mode 100644
index 000000000..e10be31a0
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTOToReturn.java
@@ -0,0 +1,36 @@
+package ru.practicum.shareit.booking.dto;
+
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+import ru.practicum.shareit.booking.model.Status;
+
+import java.time.LocalDateTime;
+
+@Data
+@Validated
+public class BookingDTOToReturn {
+
+ private long id;
+
+ private LocalDateTime start;
+
+ private LocalDateTime end;
+
+ private Item item;
+
+ private User booker;
+
+ private Status status;
+
+ @Data
+ public static class User {
+ private final long id;
+ private final String name;
+ }
+
+ @Data
+ public static class Item {
+ private final long id;
+ private final String name;
+ }
+}
\ No newline at end of file
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 861de9e01..000000000
--- 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/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java
new file mode 100644
index 000000000..dc5399e84
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java
@@ -0,0 +1,44 @@
+package ru.practicum.shareit.booking.model;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.model.User;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "bookings", schema = "public")
+@Getter
+@Setter
+@ToString
+@RequiredArgsConstructor
+public class Booking {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "start_date")
+ private LocalDateTime start;
+
+ @Column(name = "end_date")
+ private LocalDateTime end;
+
+ @ManyToOne
+ @JoinColumn(name = "item_id")
+ @ToString.Exclude
+ private Item item;
+
+ @ManyToOne
+ @JoinColumn(name = "booker_id")
+ @ToString.Exclude
+ private User booker;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false)
+ private Status status;
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/model/State.java b/src/main/java/ru/practicum/shareit/booking/model/State.java
new file mode 100644
index 000000000..2ecd29755
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/model/State.java
@@ -0,0 +1,10 @@
+package ru.practicum.shareit.booking.model;
+
+public enum State {
+ ALL,
+ CURRENT,
+ PAST,
+ FUTURE,
+ REJECTED,
+ WAITING,
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/model/Status.java b/src/main/java/ru/practicum/shareit/booking/model/Status.java
new file mode 100644
index 000000000..3229f84ec
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/model/Status.java
@@ -0,0 +1,8 @@
+package ru.practicum.shareit.booking.model;
+
+public enum Status {
+ WAITING,
+ APPROVED,
+ REJECTED,
+ CANCELED
+}
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 000000000..f31050610
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java
@@ -0,0 +1,18 @@
+package ru.practicum.shareit.booking.service;
+
+import ru.practicum.shareit.booking.dto.BookingDTO;
+import ru.practicum.shareit.booking.dto.BookingDTOToReturn;
+
+import java.util.List;
+
+public interface BookingService {
+ BookingDTOToReturn add(Long userId, BookingDTO bookingDto);
+
+ BookingDTOToReturn update(Long userId, Long bookingId, Boolean approved);
+
+ BookingDTOToReturn get(Long bookingId, Long userId);
+
+ List getByBooker(Long usersId, String status, Integer page, Integer size);
+
+ List getByOwner(Long usersId, String status, Integer page, Integer size);
+}
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 000000000..703fe32f6
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java
@@ -0,0 +1,268 @@
+package ru.practicum.shareit.booking.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.shareit.booking.BookingMapper;
+import ru.practicum.shareit.booking.BookingRepository;
+import ru.practicum.shareit.booking.dto.BookingDTO;
+import ru.practicum.shareit.booking.dto.BookingDTOToReturn;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.model.State;
+import ru.practicum.shareit.booking.model.Status;
+import ru.practicum.shareit.exception.BadRequestException;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.exception.StatusBadRequestException;
+import ru.practicum.shareit.item.ItemRepository;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.UserRepository;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+@Service
+@Transactional(readOnly = true)
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Slf4j
+public class BookingServiceImpl implements BookingService {
+
+ private final BookingRepository bRepository;
+ private final ItemRepository iRepository;
+ private final UserRepository uRepository;
+
+ @Transactional
+ @Override
+ public BookingDTOToReturn add(Long userId, BookingDTO bookingDto) {
+ Item item = iRepository.findById(bookingDto.getItemId())
+ .orElseThrow(() -> new NotFoundException("Item not found"));
+ User user = uRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("User not found"));
+ if (!item.getAvailable()) {
+ throw new BadRequestException("You can not book this item");
+ }
+ if (Objects.equals(item.getOwner().getId(), userId)) {
+ throw new NotFoundException("You cannot book your item");
+ }
+ if (bookingDto.getEnd().isBefore(LocalDateTime.now()) ||
+ bookingDto.getStart().isBefore(LocalDateTime.now()) ||
+ (bookingDto.getEnd().isBefore(bookingDto.getStart()) &&
+ !bookingDto.getEnd().equals(bookingDto.getStart()))) {
+ throw new BadRequestException("Wrong date");
+ }
+ Booking booking = bRepository.save(BookingMapper.toBooking(bookingDto, item, user));
+ return BookingMapper.toBookingDtoFrom(booking);
+ }
+
+ @Transactional
+ @Override
+ public BookingDTOToReturn update(Long userId, Long bookingId, Boolean approved) {
+ Booking booking;
+ try {
+ booking = bRepository.getReferenceById(bookingId);
+ log.info(booking.toString());
+ } catch (NullPointerException e) {
+ throw new NotFoundException("Booking not found");
+ }
+ Long ownerId = booking.getItem().getOwner().getId();
+
+ if (!Objects.equals(userId, ownerId)) {
+ throw new NotFoundException("No rights");
+ }
+
+ if (!Objects.equals(String.valueOf(booking.getStatus()), "WAITING")) {
+ throw new BadRequestException("Status has already been changed");
+ }
+
+ if (approved) {
+ booking.setStatus(Status.APPROVED);
+ } else {
+ booking.setStatus(Status.REJECTED);
+ }
+ return BookingMapper.toBookingDtoFrom(booking);
+ }
+
+ @Override
+ public BookingDTOToReturn get(Long userId, Long bookingId) {
+ Booking booking = bRepository.findById(bookingId)
+ .orElseThrow(() -> new NotFoundException(("Booking not found")));
+ Long ownerId = booking.getItem().getOwner().getId();
+ Long bookerId = booking.getBooker().getId();
+ if (Objects.equals(ownerId, userId) || Objects.equals(bookerId, userId)) {
+ return BookingMapper.toBookingDtoFrom(booking);
+ }
+ throw new NotFoundException("No rights");
+ }
+
+ @Override
+ public List getByBooker(Long userId, String status, Integer page, Integer size) {
+ Optional booker = uRepository.findById(userId);
+ Pageable pageable;
+ if (booker.isEmpty()) {
+ throw new NotFoundException("No rights");
+ }
+ User user = booker.get();
+ if (page != null && size != null) {
+ pageable = PageRequest.of(page / size, size);
+ return findBookingByBookerByPage(user, status, pageable);
+ } else {
+ return findBookingByBooker(user.getId(), status);
+ }
+ }
+
+ private List findBookingByBooker(Long userId, String status) {
+ User booker = uRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("No rights"));
+ List bookingsByBooker;
+ State state = stateValidation(status);
+ switch (state) {
+ case ALL:
+ bookingsByBooker = bRepository.findByBookerOrderByStartDesc(booker);
+ break;
+ case CURRENT:
+ bookingsByBooker = bRepository.findByBookerAndStartBeforeAndEndAfterOrderByStartDesc(booker,
+ LocalDateTime.now(), LocalDateTime.now());
+ break;
+ case PAST:
+ bookingsByBooker = bRepository.findByBookerAndStartBeforeAndEndBeforeOrderByStartDesc(booker,
+ LocalDateTime.now(), LocalDateTime.now());
+ break;
+ case FUTURE:
+ bookingsByBooker = bRepository.findByBookerAndStartAfterOrderByStartDesc(booker,
+ LocalDateTime.now());
+ break;
+ case WAITING:
+ bookingsByBooker = bRepository.findByBookerAndStatusOrderByStartDesc(booker, Status.WAITING);
+ break;
+ case REJECTED:
+ bookingsByBooker = bRepository.findByBookerAndStatusOrderByStartDesc(booker, Status.REJECTED);
+ break;
+ default:
+ throw new StatusBadRequestException("Unknown state: UNSUPPORTED_STATUS");
+ }
+ return BookingMapper.mapToBookingDtoFrom(bookingsByBooker);
+ }
+
+ private List findBookingByBookerByPage(User booker, String status, Pageable pageable) {
+ Page bookingsPage;
+ State state = stateValidation(status);
+ switch (state) {
+ case ALL:
+ bookingsPage = bRepository.findByBookerOrderByStartDesc(booker, pageable);
+ break;
+ case CURRENT:
+ bookingsPage = bRepository.findByBookerAndStartBeforeAndEndAfterOrderByStartDesc(booker,
+ LocalDateTime.now(), LocalDateTime.now(), pageable);
+ break;
+ case PAST:
+ bookingsPage = bRepository.findByBookerAndStartBeforeAndEndBeforeOrderByStartDesc(booker,
+ LocalDateTime.now(), LocalDateTime.now(), pageable);
+ break;
+ case FUTURE:
+ bookingsPage = bRepository.findByBookerAndStartAfterOrderByStartDesc(booker, LocalDateTime.now(), pageable);
+ break;
+ case WAITING:
+ bookingsPage = bRepository.findByBookerAndStatusOrderByStartDesc(booker, Status.WAITING, pageable);
+ break;
+ case REJECTED:
+ bookingsPage = bRepository.findByBookerAndStatusOrderByStartDesc(booker, Status.REJECTED, pageable);
+ break;
+ default:
+ throw new StatusBadRequestException("Unknown state: UNSUPPORTED_STATUS");
+
+ }
+ return BookingMapper.mapToBookingDtoFrom(bookingsPage);
+
+ }
+
+ @Override
+ public List getByOwner(Long userId, String status, Integer page, Integer size) {
+ User owner = uRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found"));
+ Pageable pageable;
+ if (page != null && size != null) {
+ pageable = PageRequest.of(page / size, size);
+
+ return findBookingByOwnerByPage(userId, status, pageable);
+ } else {
+ return findBookingByOwner(userId, status);
+ }
+ }
+
+ private List findBookingByOwnerByPage(Long userId, String status, Pageable pageable) {
+ if (uRepository.findById(userId).isEmpty()) {
+ throw new NotFoundException("User not found");
+ }
+ List bookingsByOwner;
+ State state = stateValidation(status);
+ switch (state) {
+ case ALL:
+ bookingsByOwner = bRepository.findByOwnerAll(userId, pageable);
+ break;
+ case CURRENT:
+ bookingsByOwner = bRepository.findByOwnerAndCurrent(userId, LocalDateTime.now(), pageable);
+ break;
+ case PAST:
+ bookingsByOwner = bRepository.findByOwnerAndPast(userId, LocalDateTime.now(), pageable);
+ break;
+ case FUTURE:
+ bookingsByOwner = bRepository.findByUserAndFuture(userId, LocalDateTime.now(), pageable);
+ break;
+ case WAITING:
+ bookingsByOwner = bRepository.findByOwnerAndByStatus(userId, String.valueOf(Status.WAITING), pageable);
+ break;
+ case REJECTED:
+ bookingsByOwner = bRepository.findByOwnerAndByStatus(userId, String.valueOf(Status.REJECTED), pageable);
+ break;
+ default:
+ throw new StatusBadRequestException("Unknown state: UNSUPPORTED_STATUS");
+ }
+ return BookingMapper.mapToBookingDtoFrom(bookingsByOwner);
+ }
+
+ private List findBookingByOwner(Long userId, String status) {
+ if (uRepository.findById(userId).isEmpty()) {
+ throw new NotFoundException("User not found");
+ }
+ List bookingsByOwner;
+ State state = stateValidation(status);
+ switch (state) {
+ case ALL:
+ bookingsByOwner = bRepository.findByOwnerAll(userId);
+ break;
+ case CURRENT:
+ bookingsByOwner = bRepository.findByOwnerAndCurrent(userId, LocalDateTime.now());
+ break;
+ case PAST:
+ bookingsByOwner = bRepository.findByOwnerAndPast(userId, LocalDateTime.now());
+ break;
+ case FUTURE:
+ bookingsByOwner = bRepository.findByUserAndFuture(userId, LocalDateTime.now());
+ break;
+ case WAITING:
+ bookingsByOwner = bRepository.findByOwnerAndByStatus(userId, Status.WAITING);
+ break;
+ case REJECTED:
+ bookingsByOwner = bRepository.findByOwnerAndByStatus(userId, Status.REJECTED);
+ break;
+ default:
+ throw new StatusBadRequestException("Unknown state: UNSUPPORTED_STATUS");
+ }
+ return BookingMapper.mapToBookingDtoFrom(bookingsByOwner);
+ }
+
+ private State stateValidation(String state) {
+ try {
+ Enum.valueOf(State.class, state);
+ return State.valueOf(state);
+ } catch (IllegalArgumentException e) {
+ throw new StatusBadRequestException("Unknown state: UNSUPPORTED_STATUS");
+ }
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/exception/BadRequestException.java b/src/main/java/ru/practicum/shareit/exception/BadRequestException.java
new file mode 100644
index 000000000..2d29b341e
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/BadRequestException.java
@@ -0,0 +1,7 @@
+package ru.practicum.shareit.exception;
+
+public class BadRequestException extends IllegalArgumentException {
+ public BadRequestException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java
new file mode 100644
index 000000000..087b580dc
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java
@@ -0,0 +1,80 @@
+package ru.practicum.shareit.exception;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageConversionException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.validation.ConstraintViolationException;
+import java.util.Map;
+
+@RestControllerAdvice
+@Slf4j
+public class ErrorHandler {
+
+ @ExceptionHandler({HttpMessageConversionException.class, BadRequestException.class})
+ public ResponseEntity