diff --git a/.gitignore b/.gitignore
index 549e00a..e45a4b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,8 @@ build/
### VS Code ###
.vscode/
+
+shareIt.mv.db
+shareIt.trace.db
+db/shareIt.mv.db
+db/*.db
\ No newline at end of file
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..35ebf89
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,17 @@
+services:
+ db:
+ image: postgres:16.1
+ container_name: postgres5
+ ports:
+ - "5438:5432"
+ volumes:
+ - ./volumes/postgres:/var/lib/postgresql/data/
+ environment:
+ - POSTGRES_DB=shareitdb
+ - POSTGRES_USER=dbuser
+ - POSTGRES_PASSWORD=12345
+ healthcheck:
+ test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER
+ timeout: 5s
+ interval: 5s
+ retries: 10
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 2db888c..b1f2886 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,247 +1,251 @@
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.3.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
+ 3.3.2
+
+
- ru.practicum
- shareit
- 0.0.1-SNAPSHOT
+ ru.practicum
+ shareit
+ 0.0.1-SNAPSHOT
- ShareIt
+ ShareIt
-
- 21
-
+
+ 21
+
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
- org.springframework.boot
- spring-boot-configuration-processor
- true
-
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
-
- org.postgresql
- postgresql
- runtime
-
+
+ org.projectlombok
+ lombok
+ true
+
-
- org.projectlombok
- lombok
- true
-
+
+ com.h2database
+ h2
+ runtime
+
-
- com.h2database
- h2
- test
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ 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.8.5.0
-
- Max
- High
-
-
-
-
- check
-
-
-
-
-
- org.jacoco
- jacoco-maven-plugin
- 0.8.12
-
-
-
-
-
- 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
-
-
-
-
-
+
+
+
+ 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.8.5.0
+
+ Max
+ High
+
+
+
+
+ check
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+
+
+
+ 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
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java
deleted file mode 100644
index f48b024..0000000
--- a/src/main/java/ru/practicum/shareit/booking/Booking.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package ru.practicum.shareit.booking;
-
-import lombok.AllArgsConstructor;
-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.
- */
-@AllArgsConstructor
-@Data
-public class Booking {
- private Long id;
- private LocalDateTime bookingStartDate;
- private LocalDateTime endDateOfBooking;
- private Item item;
- private User booker;
- private RentalStatus rentalStatus;
-}
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java
deleted file mode 100644
index b94493d..0000000
--- a/src/main/java/ru/practicum/shareit/booking/BookingController.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package ru.practicum.shareit.booking;
-
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * TODO Sprint add-bookings.
- */
-@RestController
-@RequestMapping(path = "/bookings")
-public class BookingController {
-}
diff --git a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java
new file mode 100644
index 0000000..a6a71d7
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java
@@ -0,0 +1,49 @@
+package ru.practicum.shareit.booking.controller;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.booking.dto.BookingRequestDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.service.BookingService;
+
+import java.util.List;
+
+@RequiredArgsConstructor
+@Slf4j
+@RestController
+@RequestMapping(path = "/bookings")
+public class BookingController {
+ private final BookingService bookingService;
+
+ @PostMapping
+ public BookingResponseDto createBooking(@RequestHeader("X-Sharer-User-Id") Long userId, @Valid @RequestBody BookingRequestDto bookingRequestDto) {
+ log.info("POST /bookings");
+ return bookingService.createBooking(userId, bookingRequestDto);
+ }
+
+ @PatchMapping("/{bookingId}")
+ public BookingResponseDto updateBooking(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable("bookingId") Long bookingId, @RequestParam(name = "approved") Boolean approved) {
+ log.info("PATCH /bookings/{}", bookingId);
+ return bookingService.updateBooking(userId, bookingId, approved);
+ }
+
+ @GetMapping("/{bookingId}")
+ public BookingResponseDto findBookingById(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable("bookingId") Long bookingId) {
+ log.info("GET /bookings/{}", bookingId);
+ return bookingService.findBookingById(userId, bookingId);
+ }
+
+ @GetMapping
+ public List findUserBookings(@RequestHeader("X-Sharer-User-Id") Long userId, @RequestParam(name = "state", defaultValue = "ALL") String state) {
+ log.info("GET /bookings?state={}.", state);
+ return bookingService.findUserBookings(userId, state);
+ }
+
+ @GetMapping("/owner")
+ public List findOwnerReservedItems(@RequestHeader("X-Sharer-User-Id") Long userId, @RequestParam(name = "state", defaultValue = "ALL") String state) {
+ log.info("GET /bookings/owner?state={}.", state);
+ return bookingService.findOwnerReservedItems(userId, 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/BookingRequestDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingRequestDto.java
new file mode 100644
index 0000000..e311cde
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingRequestDto.java
@@ -0,0 +1,24 @@
+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.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class BookingRequestDto {
+ @NotNull
+ private Long itemId;
+ @FutureOrPresent
+ @NotNull
+ private LocalDateTime start;
+ @Future
+ @NotNull
+ private LocalDateTime end;
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java
new file mode 100644
index 0000000..334abb1
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java
@@ -0,0 +1,25 @@
+package ru.practicum.shareit.booking.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import ru.practicum.shareit.booking.status.RentalStatus;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.dto.UserDto;
+
+import java.time.LocalDateTime;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class BookingResponseDto {
+ 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 ItemDto item;
+ private UserDto booker;
+ private RentalStatus status;
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/SimplifiedBookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/SimplifiedBookingDto.java
new file mode 100644
index 0000000..07909fa
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/SimplifiedBookingDto.java
@@ -0,0 +1,13 @@
+package ru.practicum.shareit.booking.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class SimplifiedBookingDto {
+ private Long id;
+ private Long bookerId;
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/mapper/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/dto/mapper/BookingMapper.java
new file mode 100644
index 0000000..e015d27
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/mapper/BookingMapper.java
@@ -0,0 +1,57 @@
+package ru.practicum.shareit.booking.dto.mapper;
+
+import lombok.experimental.UtilityClass;
+import ru.practicum.shareit.booking.dto.BookingRequestDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.dto.SimplifiedBookingDto;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.status.RentalStatus;
+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;
+
+@UtilityClass
+public class BookingMapper {
+ public static BookingResponseDto toBookingDto(Booking booking) {
+ ItemDto itemDto = new ItemDto(booking.getItem().getId(),
+ booking.getItem().getName(),
+ booking.getItem().getDescription(),
+ booking.getItem().getAvailable()
+ );
+
+ UserDto bookerDto = new UserDto(
+ booking.getBooker().getId(),
+ booking.getBooker().getName(),
+ booking.getBooker().getEmail()
+ );
+
+ return new BookingResponseDto(
+ booking.getId(),
+ booking.getStart(),
+ booking.getEnd(),
+ itemDto,
+ bookerDto,
+ booking.getStatus()
+ );
+ }
+
+ public static Booking toBooking(BookingRequestDto bookingRequestDto, Item item, User booker, RentalStatus status) {
+ return new Booking(
+ null,
+ bookingRequestDto.getStart(),
+ bookingRequestDto.getEnd(),
+ item,
+ booker,
+ status
+ );
+ }
+
+ public static SimplifiedBookingDto toSimplifiedBookingDto(Booking booking) {
+ if (booking == null) return null;
+ return new SimplifiedBookingDto(
+ booking.getId(),
+ booking.getBooker().getId()
+ );
+ }
+}
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..e7c3816
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.booking.model;
+
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.shareit.booking.status.RentalStatus;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.LocalDateTime;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Entity
+@Table(name = "bookings")
+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(fetch = FetchType.LAZY)
+ @JoinColumn(name = "item_id", nullable = false)
+ private Item item;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "booker_id", nullable = false)
+ private User booker;
+ @Enumerated(EnumType.STRING)
+ private RentalStatus 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..51c9335
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java
@@ -0,0 +1,49 @@
+package ru.practicum.shareit.booking.repository;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.status.RentalStatus;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface BookingRepository extends JpaRepository {
+ @Query("SELECT b FROM Booking AS b WHERE b.booker = :booker AND CURRENT_TIMESTAMP BETWEEN b.start AND b.end")
+ List findCurrentBookings(@Param("booker") User booker, Sort sort);
+
+ @Query("SELECT b FROM Booking AS b WHERE b.booker = :booker AND b.end < CURRENT_TIMESTAMP")
+ List findPastBookings(@Param("booker") User booker, Sort sort);
+
+ @Query("SELECT b FROM Booking AS b WHERE b.booker = :booker AND b.start > CURRENT_TIMESTAMP")
+ List findFutureBookings(@Param("booker") User booker, Sort sort);
+
+ List findByBookerAndStatus(User booker, RentalStatus status, Sort sort);
+
+ List findByBooker(User booker, Sort sort);
+
+ @Query("SELECT b FROM Booking AS b WHERE b.item.owner = :owner AND CURRENT_TIMESTAMP BETWEEN b.start AND b.end")
+ List findCurrentItemsBookings(@Param("owner") User owner, Sort sort);
+
+ @Query("SELECT b FROM Booking AS b WHERE b.item.owner = :owner AND b.end < CURRENT_TIMESTAMP")
+ List findPastItemsBookings(@Param("owner") User owner, Sort sort);
+
+ @Query("SELECT b FROM Booking AS b WHERE b.item.owner = :owner AND b.start > CURRENT_TIMESTAMP")
+ List findFutureItemsBookings(@Param("owner") User owner, Sort sort);
+
+ List findByItemOwnerAndStatus(User owner, RentalStatus status, Sort sort);
+
+ @Query("SELECT b FROM Booking AS b WHERE b.item.owner = :owner")
+ List findByItemOwner(@Param("owner") User owner, Sort sort);
+
+ Booking findFirstByBookerIdAndItemIdAndEndIsBefore(Long bookerId, Long itemId, LocalDateTime end, Sort sort);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id IN :itemIds AND b.end <= CURRENT_TIMESTAMP")
+ List findLastBookingsForItems(@Param("itemIds") List itemIds, Sort sort);
+
+ @Query("SELECT b FROM Booking b WHERE b.item.id IN :itemIds AND b.start >= CURRENT_TIMESTAMP")
+ List findNextBookingsForItems(@Param("itemIds") List itemIds, Sort sort);
+}
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..9fb6c87
--- /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.BookingRequestDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+
+import java.util.List;
+
+public interface BookingService {
+ BookingResponseDto createBooking(Long userId, BookingRequestDto bookingRequestDto);
+
+ BookingResponseDto updateBooking(Long userId, Long bookingId, Boolean approved);
+
+ BookingResponseDto findBookingById(Long userId, Long bookingId);
+
+ List findUserBookings(Long userId, String state);
+
+ List findOwnerReservedItems(Long userId, String state);
+}
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..327df26
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java
@@ -0,0 +1,176 @@
+package ru.practicum.shareit.booking.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.booking.dto.BookingRequestDto;
+import ru.practicum.shareit.booking.dto.BookingResponseDto;
+import ru.practicum.shareit.booking.dto.mapper.BookingMapper;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
+import ru.practicum.shareit.booking.status.RentalStatus;
+import ru.practicum.shareit.exception.NotFoundException;
+import ru.practicum.shareit.exception.ValidationException;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.item.repository.ItemRepository;
+import ru.practicum.shareit.user.model.User;
+import ru.practicum.shareit.user.repository.UserRepository;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Slf4j
+@Service
+public class BookingServiceImpl implements BookingService {
+ private final BookingRepository bookingRepository;
+ private final UserRepository userRepository;
+ private final ItemRepository itemRepository;
+
+ @Override
+ public BookingResponseDto createBooking(Long userId, BookingRequestDto bookingRequestDto) {
+ User booker = validateUser(userId);
+ Item item = validateItem(bookingRequestDto.getItemId());
+
+ if (Objects.equals(item.getOwner().getId(), userId)) {
+ log.warn("Владелец не может бронировать свою вещь");
+ throw new ValidationException("Владелец не может бронировать свою вещь");
+ }
+ if (!item.getAvailable()) {
+ log.warn("Данная вещь недоступна для бронирования");
+ throw new ValidationException("Данная вещь недоступна для бронирования");
+ }
+
+ if (!bookingRequestDto.getStart().isBefore(bookingRequestDto.getEnd())) {
+ log.warn("Период указан неверно");
+ throw new ValidationException("Период указан неверно");
+ }
+
+ RentalStatus status = RentalStatus.WAITING;
+
+ return BookingMapper.toBookingDto(bookingRepository.save(BookingMapper.toBooking(bookingRequestDto, item, booker, status)));
+ }
+
+ @Override
+ public BookingResponseDto updateBooking(Long userId, Long bookingId, Boolean approved) {
+ validateForbiddenUser(userId);
+ Booking booking = validateBooking(bookingId);
+
+ if (!Objects.equals(userId, booking.getItem().getOwner().getId())) {
+ log.warn("Обновить аренду может только владелец вещи");
+ throw new ValidationException("Обновить аренду может только владелец вещи");
+ }
+
+ if (!Objects.equals(booking.getStatus(), RentalStatus.WAITING)) {
+ log.warn("Статус уже был установлен");
+ throw new ValidationException("Нельзя изменить статус: бронирование уже подтверждено или отклонено");
+ }
+
+ booking.setStatus(approved ? RentalStatus.APPROVED : RentalStatus.REJECTED);
+
+ return BookingMapper.toBookingDto(bookingRepository.save(booking));
+ }
+
+ @Override
+ public BookingResponseDto findBookingById(Long userId, Long bookingId) {
+ validateUser(userId);
+ Booking booking = validateBooking(bookingId);
+
+ if (!Objects.equals(booking.getItem().getOwner().getId(), userId) && !Objects.equals(booking.getBooker().getId(), userId)) {
+ log.warn("Данный пользователь не может получить информацию по бронированию");
+ throw new ValidationException("Данный пользователь не может получить информацию по бронированию");
+ }
+
+ return BookingMapper.toBookingDto(booking);
+ }
+
+ @Override
+ public List findUserBookings(Long userId, String state) {
+ User booker = validateUser(userId);
+
+ List bookerBookings;
+ Sort sortDescByStart = Sort.by(Sort.Direction.DESC, "start");
+
+ switch (state) {
+ case "CURRENT":
+ bookerBookings = bookingRepository.findCurrentBookings(booker, sortDescByStart);
+ break;
+ case "PAST":
+ bookerBookings = bookingRepository.findPastBookings(booker, sortDescByStart);
+ break;
+ case "FUTURE":
+ bookerBookings = bookingRepository.findFutureBookings(booker, sortDescByStart);
+ break;
+ case "WAITING":
+ bookerBookings = bookingRepository.findByBookerAndStatus(booker, RentalStatus.WAITING, sortDescByStart);
+ break;
+ case "REJECTED":
+ bookerBookings = bookingRepository.findByBookerAndStatus(booker, RentalStatus.REJECTED, sortDescByStart);
+ break;
+ case "ALL":
+ default:
+ bookerBookings = bookingRepository.findByBooker(booker, sortDescByStart);
+ break;
+ }
+
+ return bookerBookings.stream()
+ .map(BookingMapper::toBookingDto)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List findOwnerReservedItems(Long userId, String state) {
+ User owner = validateUser(userId);
+
+ List ownerBookings;
+ Sort sortDescByStart = Sort.by(Sort.Direction.DESC, "start");
+
+ switch (state) {
+ case "CURRENT":
+ ownerBookings = bookingRepository.findCurrentItemsBookings(owner, sortDescByStart);
+ break;
+ case "PAST":
+ ownerBookings = bookingRepository.findPastItemsBookings(owner, sortDescByStart);
+ break;
+ case "FUTURE":
+ ownerBookings = bookingRepository.findFutureItemsBookings(owner, sortDescByStart);
+ break;
+ case "WAITING":
+ ownerBookings = bookingRepository.findByItemOwnerAndStatus(owner, RentalStatus.WAITING, sortDescByStart);
+ break;
+ case "REJECTED":
+ ownerBookings = bookingRepository.findByItemOwnerAndStatus(owner, RentalStatus.REJECTED, sortDescByStart);
+ break;
+ case "ALL":
+ default:
+ ownerBookings = bookingRepository.findByItemOwner(owner, sortDescByStart);
+ break;
+ }
+
+ return ownerBookings.stream()
+ .map(BookingMapper::toBookingDto)
+ .collect(Collectors.toList());
+ }
+
+ private void validateForbiddenUser(Long userId) {
+ userRepository.findById(userId)
+ .orElseThrow(() -> new ValidationException("Пользователю с данным id: " + userId + " доступ запрещен"));
+ }
+
+ private User validateUser(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Пользователь с данным id: " + userId + " не найден"));
+ }
+
+ private Item validateItem(Long itemId) {
+ return itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Вещь с данным id: " + itemId + " не найден"));
+ }
+
+ private Booking validateBooking(Long bookingId) {
+ return bookingRepository.findById(bookingId)
+ .orElseThrow(() -> new NotFoundException("Бронирование с данным id:" + bookingId + " не найдено"));
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/booking/RentalStatus.java b/src/main/java/ru/practicum/shareit/booking/status/RentalStatus.java
similarity index 65%
rename from src/main/java/ru/practicum/shareit/booking/RentalStatus.java
rename to src/main/java/ru/practicum/shareit/booking/status/RentalStatus.java
index 5d4afad..a36dfb1 100644
--- a/src/main/java/ru/practicum/shareit/booking/RentalStatus.java
+++ b/src/main/java/ru/practicum/shareit/booking/status/RentalStatus.java
@@ -1,4 +1,4 @@
-package ru.practicum.shareit.booking;
+package ru.practicum.shareit.booking.status;
public enum RentalStatus {
WAITING,
diff --git a/src/main/java/ru/practicum/shareit/error/ErrorResponse.java b/src/main/java/ru/practicum/shareit/error/ErrorResponse.java
index 76b0cb2..637fb8c 100644
--- a/src/main/java/ru/practicum/shareit/error/ErrorResponse.java
+++ b/src/main/java/ru/practicum/shareit/error/ErrorResponse.java
@@ -5,7 +5,7 @@
@AllArgsConstructor
@Getter
-class ErrorResponse {
+public class ErrorResponse {
private final String error;
private final String description;
}
diff --git a/src/main/java/ru/practicum/shareit/item/comment/dto/CommentRequestDto.java b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentRequestDto.java
new file mode 100644
index 0000000..7943bae
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentRequestDto.java
@@ -0,0 +1,15 @@
+package ru.practicum.shareit.item.comment.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.*;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class CommentRequestDto {
+ @Size(max = 2000, message = "Комментарий не может быть длиннее 2000 символов")
+ @NotBlank(message = "Комментарий не может быть пустым")
+ private String text;
+}
diff --git a/src/main/java/ru/practicum/shareit/item/comment/dto/CommentResponseDto.java b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentResponseDto.java
new file mode 100644
index 0000000..5708e07
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentResponseDto.java
@@ -0,0 +1,20 @@
+package ru.practicum.shareit.item.comment.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class CommentResponseDto {
+ private Long id;
+ private String text;
+ private String authorName;
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
+ private LocalDateTime created;
+ private Long itemId;
+}
diff --git a/src/main/java/ru/practicum/shareit/item/comment/dto/mapper/CommentMapper.java b/src/main/java/ru/practicum/shareit/item/comment/dto/mapper/CommentMapper.java
new file mode 100644
index 0000000..09b3455
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/dto/mapper/CommentMapper.java
@@ -0,0 +1,33 @@
+package ru.practicum.shareit.item.comment.dto.mapper;
+
+import lombok.experimental.UtilityClass;
+import ru.practicum.shareit.item.comment.dto.CommentRequestDto;
+import ru.practicum.shareit.item.comment.dto.CommentResponseDto;
+import ru.practicum.shareit.item.comment.model.Comment;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.LocalDateTime;
+
+@UtilityClass
+public class CommentMapper {
+ public static CommentResponseDto toCommentResponseDto(Comment comment) {
+ return new CommentResponseDto(
+ comment.getId(),
+ comment.getText(),
+ comment.getAuthor().getName(),
+ comment.getCreated(),
+ comment.getItem().getId()
+ );
+ }
+
+ public static Comment toComment(CommentRequestDto commentRequestDto, User author, Item item) {
+ return new Comment(
+ null,
+ commentRequestDto.getText(),
+ item,
+ author,
+ LocalDateTime.now()
+ );
+ }
+}
diff --git a/src/main/java/ru/practicum/shareit/item/comment/model/Comment.java b/src/main/java/ru/practicum/shareit/item/comment/model/Comment.java
new file mode 100644
index 0000000..42371fb
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/model/Comment.java
@@ -0,0 +1,28 @@
+package ru.practicum.shareit.item.comment.model;
+
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.LocalDateTime;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+@Entity
+@Table(name = "comments")
+public class Comment {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String text;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "item_id", nullable = false)
+ private Item item;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "author_id", nullable = false)
+ private User author;
+ private LocalDateTime created;
+}
diff --git a/src/main/java/ru/practicum/shareit/item/comment/repository/CommentRepository.java b/src/main/java/ru/practicum/shareit/item/comment/repository/CommentRepository.java
new file mode 100644
index 0000000..59fad89
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/repository/CommentRepository.java
@@ -0,0 +1,10 @@
+package ru.practicum.shareit.item.comment.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.shareit.item.comment.model.Comment;
+
+import java.util.List;
+
+public interface CommentRepository extends JpaRepository {
+ List findByItemIdIn(List itemIds);
+}
diff --git a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
index 1f86c0f..a0293d0 100644
--- a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
+++ b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
@@ -4,6 +4,9 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.item.comment.dto.CommentRequestDto;
+import ru.practicum.shareit.item.comment.dto.CommentResponseDto;
+import ru.practicum.shareit.item.dto.ItemBookingDto;
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.service.ItemService;
@@ -29,13 +32,13 @@ public ItemDto updateItem(@RequestHeader("X-Sharer-User-Id") Long userId, @PathV
}
@GetMapping("/{itemId}")
- public ItemDto findItemById(@PathVariable("itemId") Long itemId) {
+ public ItemBookingDto findItemById(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable("itemId") Long itemId) {
log.info("GET /items/{}", itemId);
- return itemService.findItemById(itemId);
+ return itemService.findItemById(userId, itemId);
}
@GetMapping
- public List findUserItems(@RequestHeader("X-Sharer-User-Id") Long userId) {
+ public List findUserItems(@RequestHeader("X-Sharer-User-Id") Long userId) {
log.info("GET /items");
return itemService.findUserItems(userId);
}
@@ -45,4 +48,12 @@ public List searchItem(@RequestParam(name = "text") String text) {
log.info("GET /items/search?text={}.", text);
return itemService.searchItem(text);
}
+
+ @PostMapping("/{itemId}/comment")
+ public CommentResponseDto createComment(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable("itemId") Long itemId,
+ @Valid @RequestBody CommentRequestDto commentRequestDto) {
+ log.info("POST /items/{}/comment", itemId);
+ return itemService.createComment(userId, itemId, commentRequestDto);
+ }
}
diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemStorage.java b/src/main/java/ru/practicum/shareit/item/dao/ItemStorage.java
deleted file mode 100644
index f539e57..0000000
--- a/src/main/java/ru/practicum/shareit/item/dao/ItemStorage.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package ru.practicum.shareit.item.dao;
-
-import ru.practicum.shareit.item.model.Item;
-
-import java.util.List;
-
-public interface ItemStorage {
- Item createItem(Item item);
-
- Item updateItem(Long userId, Item updateItem);
-
- Item findItemById(Long itemId);
-
- List- findUserItems();
-
- List
- searchItem();
-}
diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemStorageImpl.java b/src/main/java/ru/practicum/shareit/item/dao/ItemStorageImpl.java
deleted file mode 100644
index 3eef63e..0000000
--- a/src/main/java/ru/practicum/shareit/item/dao/ItemStorageImpl.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package ru.practicum.shareit.item.dao;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Repository;
-import ru.practicum.shareit.exception.NotFoundException;
-import ru.practicum.shareit.item.model.Item;
-
-import java.util.*;
-
-@Slf4j
-@Repository
-public class ItemStorageImpl implements ItemStorage {
-
- private final Map items = new HashMap<>();
-
- @Override
- public Item createItem(Item item) {
- item.setId(getId());
- items.put(item.getId(), item);
- return item;
- }
-
- @Override
- public Item updateItem(Long userId, Item updateItem) {
- if (!items.containsKey(updateItem.getId())) {
- log.warn("Обновляемая вещь не найдена");
- throw new NotFoundException("Обновляемая вещь не найдена");
- } else {
- Item oldItem = items.get(updateItem.getId());
-
- if (updateItem.getName() == null) {
- updateItem.setName(oldItem.getName());
- }
- if (updateItem.getDescription() == null) {
- updateItem.setDescription(oldItem.getDescription());
- }
- if (updateItem.getAvailable() == null) {
- updateItem.setAvailable(oldItem.getAvailable());
- }
- }
-
- items.put(updateItem.getId(), updateItem);
- return updateItem;
- }
-
- @Override
- public Item findItemById(Long itemId) {
- return items.get(itemId);
- }
-
- @Override
- public List
- findUserItems() {
- return new ArrayList<>(items.values());
- }
-
- @Override
- public List
- searchItem() {
- return new ArrayList<>(items.values());
- }
-
- private Long getId() {
- long lastId = items.keySet().stream()
- .mapToLong(Long::longValue)
- .max()
- .orElse(0);
- return lastId + 1;
- }
-}
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemBookingDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemBookingDto.java
new file mode 100644
index 0000000..3f91c0f
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemBookingDto.java
@@ -0,0 +1,21 @@
+package ru.practicum.shareit.item.dto;
+
+import lombok.*;
+import ru.practicum.shareit.booking.dto.SimplifiedBookingDto;
+import ru.practicum.shareit.item.comment.dto.CommentResponseDto;
+
+import java.util.List;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class ItemBookingDto {
+ private Long id;
+ private String name;
+ private String description;
+ private Boolean available;
+ private SimplifiedBookingDto lastBooking;
+ private SimplifiedBookingDto 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 b17d740..a750b8a 100644
--- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
@@ -3,10 +3,12 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
-import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
@AllArgsConstructor
-@Data
+@Getter
+@Setter
public class ItemDto {
private Long id;
@NotBlank(message = "Имя не может быть null")
diff --git a/src/main/java/ru/practicum/shareit/item/dto/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/dto/mapper/ItemMapper.java
index 5a3137c..72cbccd 100644
--- a/src/main/java/ru/practicum/shareit/item/dto/mapper/ItemMapper.java
+++ b/src/main/java/ru/practicum/shareit/item/dto/mapper/ItemMapper.java
@@ -1,10 +1,15 @@
package ru.practicum.shareit.item.dto.mapper;
import lombok.experimental.UtilityClass;
+import ru.practicum.shareit.booking.dto.SimplifiedBookingDto;
+import ru.practicum.shareit.item.comment.dto.CommentResponseDto;
+import ru.practicum.shareit.item.dto.ItemBookingDto;
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.model.Item;
import ru.practicum.shareit.user.model.User;
+import java.util.List;
+
@UtilityClass
public class ItemMapper {
public static ItemDto toItemDto(Item item) {
@@ -22,19 +27,22 @@ public static Item toItem(User user, ItemDto itemDto) {
itemDto.getName(),
itemDto.getDescription(),
itemDto.getAvailable(),
- user,
- null
+ user
);
}
- public static Item toItem(Long id, User user, ItemDto itemDto) {
- return new Item(
- id,
- itemDto.getName(),
- itemDto.getDescription(),
- itemDto.getAvailable(),
- user,
- null
+ public static ItemBookingDto toItemBookingDto(Item item,
+ SimplifiedBookingDto lastBooking,
+ SimplifiedBookingDto nextBooking,
+ List comments) {
+ return new ItemBookingDto(
+ item.getId(),
+ item.getName(),
+ item.getDescription(),
+ item.getAvailable(),
+ lastBooking,
+ nextBooking,
+ comments
);
}
}
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 9499466..bf8fe78 100644
--- a/src/main/java/ru/practicum/shareit/item/model/Item.java
+++ b/src/main/java/ru/practicum/shareit/item/model/Item.java
@@ -1,17 +1,23 @@
package ru.practicum.shareit.item.model;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import ru.practicum.shareit.request.ItemRequest;
+import jakarta.persistence.*;
+import lombok.*;
import ru.practicum.shareit.user.model.User;
+@NoArgsConstructor
@AllArgsConstructor
-@Data
+@Getter
+@Setter
+@Entity
+@Table(name = "items")
public class Item {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private Boolean available;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "owner_id")
private User owner;
- private ItemRequest request;
}
diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java
new file mode 100644
index 0000000..b71ebeb
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java
@@ -0,0 +1,17 @@
+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;
+
+public interface ItemRepository extends JpaRepository
- {
+ List
- findAllByOwner(User user);
+
+ @Query("SELECT i FROM Item i " +
+ "WHERE UPPER(i.name) LIKE UPPER(CONCAT('%', ?1, '%')) " +
+ " OR UPPER(i.description) LIKE UPPER(CONCAT('%', ?1, '%'))")
+ List
- search(String text);
+}
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 bcc43e4..9a23864 100644
--- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java
+++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java
@@ -1,5 +1,8 @@
package ru.practicum.shareit.item.service;
+import ru.practicum.shareit.item.comment.dto.CommentRequestDto;
+import ru.practicum.shareit.item.comment.dto.CommentResponseDto;
+import ru.practicum.shareit.item.dto.ItemBookingDto;
import ru.practicum.shareit.item.dto.ItemDto;
import java.util.List;
@@ -9,9 +12,11 @@ public interface ItemService {
ItemDto updateItem(Long itemId, Long userId, ItemDto itemDto);
- ItemDto findItemById(Long itemId);
+ ItemBookingDto findItemById(Long userId, Long itemId);
- List findUserItems(Long userId);
+ List findUserItems(Long userId);
List searchItem(String text);
+
+ CommentResponseDto createComment(Long userId, Long itemId, CommentRequestDto commentRequestDto);
}
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 2e4c3f3..33c7743 100644
--- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java
+++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java
@@ -2,19 +2,27 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
+import ru.practicum.shareit.booking.dto.mapper.BookingMapper;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.repository.BookingRepository;
import ru.practicum.shareit.exception.NotFoundException;
import ru.practicum.shareit.exception.ValidationException;
-import ru.practicum.shareit.item.dao.ItemStorage;
+import ru.practicum.shareit.item.comment.dto.CommentRequestDto;
+import ru.practicum.shareit.item.comment.dto.CommentResponseDto;
+import ru.practicum.shareit.item.comment.dto.mapper.CommentMapper;
+import ru.practicum.shareit.item.comment.repository.CommentRepository;
+import ru.practicum.shareit.item.dto.ItemBookingDto;
+import ru.practicum.shareit.item.repository.ItemRepository;
import ru.practicum.shareit.item.dto.ItemDto;
import ru.practicum.shareit.item.dto.mapper.ItemMapper;
import ru.practicum.shareit.item.model.Item;
import ru.practicum.shareit.user.model.User;
-import ru.practicum.shareit.user.dao.UserStorage;
+import ru.practicum.shareit.user.repository.UserRepository;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
+import java.time.LocalDateTime;
+import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@@ -22,43 +30,110 @@
@Service
public class ItemServiceImpl implements ItemService {
- private final ItemStorage itemStorage;
- private final UserStorage userStorage;
+ private final ItemRepository itemRepository;
+ private final UserRepository userRepository;
+ private final BookingRepository bookingRepository;
+ private final CommentRepository commentRepository;
@Override
public ItemDto createItem(Long userId, ItemDto itemDto) {
- User user = userStorage.findUserById(userId);
- validateUser(user, userId);
+ User user = validateUser(userId);
- return ItemMapper.toItemDto(itemStorage.createItem(
+ return ItemMapper.toItemDto(itemRepository.save(
ItemMapper.toItem(user, itemDto))
);
}
@Override
public ItemDto updateItem(Long itemId, Long userId, ItemDto itemDto) {
- User user = userStorage.findUserById(userId);
- validateUser(user, userId);
+ validateUser(userId);
+ Item item = validateItem(itemId);
- if (!Objects.equals(userId, itemStorage.findItemById(itemId).getOwner().getId())) {
+ if (!Objects.equals(userId, item.getOwner().getId())) {
log.warn("Внесение изменений доступно владельцу");
throw new ValidationException("Внесение изменений доступно владельцу");
}
- Item item = ItemMapper.toItem(itemId, user, itemDto);
- return ItemMapper.toItemDto(itemStorage.updateItem(userId, item));
+ if (itemDto.getName() != null && !itemDto.getName().isBlank()) {
+ item.setName(itemDto.getName());
+ }
+ if (itemDto.getDescription() != null && !itemDto.getDescription().isBlank()) {
+ item.setDescription(itemDto.getDescription());
+ }
+ if (itemDto.getAvailable() != null) {
+ item.setAvailable(itemDto.getAvailable());
+ }
+
+ return ItemMapper.toItemDto(itemRepository.save(item));
}
@Override
- public ItemDto findItemById(Long itemId) {
- return ItemMapper.toItemDto(itemStorage.findItemById(itemId));
+ public ItemBookingDto findItemById(Long userId, Long itemId) {
+ Item item = validateItem(itemId);
+ validateUser(userId);
+
+ List comments = commentRepository.findByItemIdIn(List.of(itemId)).stream()
+ .map(CommentMapper::toCommentResponseDto)
+ .collect(Collectors.toList());
+
+ if (!Objects.equals(item.getOwner().getId(), userId)) {
+ return ItemMapper.toItemBookingDto(item, null, null, comments);
+ }
+
+ Sort sortDescByEnd = Sort.by(Sort.Direction.DESC, "end");
+ Sort sortAscByStart = Sort.by(Sort.Direction.ASC, "start");
+
+ Booking lastBooking = bookingRepository.findLastBookingsForItems(List.of(itemId), sortDescByEnd).stream()
+ .findFirst()
+ .orElse(null);
+
+ Booking nextBooking = bookingRepository.findNextBookingsForItems(List.of(itemId), sortAscByStart).stream()
+ .findFirst()
+ .orElse(null);
+
+ return ItemMapper.toItemBookingDto(
+ item,
+ BookingMapper.toSimplifiedBookingDto(lastBooking),
+ BookingMapper.toSimplifiedBookingDto(nextBooking),
+ comments
+ );
}
@Override
- public List findUserItems(Long userId) {
- return itemStorage.findUserItems().stream()
- .filter(item -> Objects.equals(item.getOwner().getId(), userId))
- .map(ItemMapper::toItemDto)
+ public List findUserItems(Long userId) {
+ User owner = validateUser(userId);
+ List
- items = itemRepository.findAllByOwner(owner);
+
+ List itemIds = items.stream()
+ .map(Item::getId)
+ .collect(Collectors.toList());
+
+ Sort sortDescByEnd = Sort.by(Sort.Direction.DESC, "end");
+ Sort sortAscByStart = Sort.by(Sort.Direction.ASC, "start");
+
+ Map lastBookingsMap = bookingRepository.findLastBookingsForItems(itemIds, sortDescByEnd).stream()
+ .collect(Collectors.toMap(lb -> lb.getItem().getId(), lb -> lb));
+
+ Map nextBookingsMap = bookingRepository.findNextBookingsForItems(itemIds, sortAscByStart).stream()
+ .collect(Collectors.toMap(nb -> nb.getItem().getId(), nb -> nb));
+
+ Map> commentsMap = commentRepository.findByItemIdIn(itemIds).stream()
+ .map(CommentMapper::toCommentResponseDto)
+ .collect(Collectors.groupingBy(CommentResponseDto::getItemId));
+
+ return items.stream()
+ .map(item -> {
+ Booking lastBooking = lastBookingsMap.get(item.getId());
+ Booking nextBooking = nextBookingsMap.get(item.getId());
+ List comments = commentsMap.getOrDefault(item.getId(), Collections.emptyList());
+
+ return ItemMapper.toItemBookingDto(
+ item,
+ BookingMapper.toSimplifiedBookingDto(lastBooking),
+ BookingMapper.toSimplifiedBookingDto(nextBooking),
+ comments
+ );
+ })
.collect(Collectors.toList());
}
@@ -68,21 +143,35 @@ public List searchItem(String text) {
return new ArrayList<>();
}
- return itemStorage.searchItem().stream()
- .filter(Objects::nonNull)
- .filter(item ->
- (item.getName() != null && item.getName().toLowerCase().contains(text.toLowerCase())) ||
- (item.getDescription() != null && item.getDescription().toLowerCase().contains(text.toLowerCase()))
- )
- .filter(item -> item.getAvailable() != null && item.getAvailable())
+ return itemRepository.search(text).stream()
+ .filter(item -> Objects.equals(item.getAvailable(), Boolean.TRUE))
.map(ItemMapper::toItemDto)
.collect(Collectors.toList());
}
- private void validateUser(User user, Long userId) {
- if (user == null) {
- log.warn("Пользователь с данным id {} не найден", userId);
- throw new NotFoundException("Пользователь с данным id: " + userId + " не найден");
+ @Override
+ public CommentResponseDto createComment(Long userId, Long itemId, CommentRequestDto commentRequestDto) {
+ User author = validateUser(userId);
+ Item item = validateItem(itemId);
+ Sort sortDescByEnd = Sort.by(Sort.Direction.DESC, "end");
+
+ Booking booking = bookingRepository.findFirstByBookerIdAndItemIdAndEndIsBefore(userId, itemId, LocalDateTime.now(), sortDescByEnd);
+
+ if (booking == null) {
+ log.warn("Пользователь может оставить комментарий после аренды");
+ throw new ValidationException("Пользователь может оставить комментарий после аренды");
}
+
+ return CommentMapper.toCommentResponseDto(commentRepository.save(CommentMapper.toComment(commentRequestDto, author, item)));
+ }
+
+ private User validateUser(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Пользователь с данным id: " + userId + " не найден"));
+ }
+
+ private Item validateItem(Long itemId) {
+ return itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Элемент с данным id: " + itemId + " не найден"));
}
}
diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java
index 13bdc7f..89006f7 100644
--- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java
+++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java
@@ -1,7 +1,8 @@
package ru.practicum.shareit.request;
import lombok.AllArgsConstructor;
-import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
import ru.practicum.shareit.user.model.User;
import java.time.LocalDateTime;
@@ -10,7 +11,8 @@
* TODO Sprint add-item-requests.
*/
@AllArgsConstructor
-@Data
+@Getter
+@Setter
public class ItemRequest {
private Long id;
private String description;
diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserStorage.java b/src/main/java/ru/practicum/shareit/user/dao/UserStorage.java
deleted file mode 100644
index 9c43da2..0000000
--- a/src/main/java/ru/practicum/shareit/user/dao/UserStorage.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package ru.practicum.shareit.user.dao;
-
-import ru.practicum.shareit.user.model.User;
-
-import java.util.List;
-
-public interface UserStorage {
-
- List findAllUsers();
-
- User findUserById(Long id);
-
- User createUser(User user);
-
- User updateUser(User updateUser);
-
- void deleteUser(Long id);
-}
diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserStorageImpl.java b/src/main/java/ru/practicum/shareit/user/dao/UserStorageImpl.java
deleted file mode 100644
index 8c764f6..0000000
--- a/src/main/java/ru/practicum/shareit/user/dao/UserStorageImpl.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package ru.practicum.shareit.user.dao;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Repository;
-import ru.practicum.shareit.exception.NotFoundException;
-import ru.practicum.shareit.user.model.User;
-
-import java.util.*;
-
-@Slf4j
-@Repository
-public class UserStorageImpl implements UserStorage {
- private Map users = new HashMap<>();
-
- @Override
- public List findAllUsers() {
- return new ArrayList<>(users.values());
- }
-
- @Override
- public User findUserById(Long id) {
- return users.get(id);
- }
-
- @Override
- public User createUser(User user) {
- user.setId(getId());
- users.put(user.getId(), user);
- return user;
- }
-
- @Override
- public User updateUser(User updateUser) {
- if (!users.containsKey(updateUser.getId())) {
- log.warn("Обновляемый пользователь не найден");
- throw new NotFoundException("Обновляемый пользователь не найден");
- } else {
- User oldUser = users.get(updateUser.getId());
- if (updateUser.getName() == null) {
- updateUser.setName(oldUser.getName());
- }
- if (updateUser.getEmail() == null) {
- updateUser.setEmail(oldUser.getEmail());
- }
- }
-
- users.put(updateUser.getId(), updateUser);
- return updateUser;
- }
-
- @Override
- public void deleteUser(Long id) {
- users.remove(id);
- }
-
- private Long getId() {
- long lastId = users.keySet().stream()
- .mapToLong(Long::longValue)
- .max()
- .orElse(0);
- return lastId + 1;
- }
-}
diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java
index 50d7144..bf78cf7 100644
--- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java
+++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java
@@ -2,13 +2,12 @@
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
@NoArgsConstructor
@AllArgsConstructor
-@Data
+@Getter
+@Setter
public class UserDto {
private Long id;
@NotBlank(message = "Имя не может быть null")
diff --git a/src/main/java/ru/practicum/shareit/user/dto/mapper/UserMapper.java b/src/main/java/ru/practicum/shareit/user/dto/mapper/UserMapper.java
index bf99f95..e357d95 100644
--- a/src/main/java/ru/practicum/shareit/user/dto/mapper/UserMapper.java
+++ b/src/main/java/ru/practicum/shareit/user/dto/mapper/UserMapper.java
@@ -21,12 +21,4 @@ public static User toUser(UserDto userDto) {
userDto.getEmail()
);
}
-
- public static User toUser(Long id, UserDto userDto) {
- return new User(
- id,
- userDto.getName(),
- userDto.getEmail()
- );
- }
}
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 cdf04eb..2751309 100644
--- a/src/main/java/ru/practicum/shareit/user/model/User.java
+++ b/src/main/java/ru/practicum/shareit/user/model/User.java
@@ -1,11 +1,17 @@
package ru.practicum.shareit.user.model;
-import lombok.AllArgsConstructor;
-import lombok.Data;
+import jakarta.persistence.*;
+import lombok.*;
+@NoArgsConstructor
@AllArgsConstructor
-@Data
+@Getter
+@Setter
+@Entity
+@Table(name = "users")
public class User {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String 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
new file mode 100644
index 0000000..c79d1e1
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java
@@ -0,0 +1,10 @@
+package ru.practicum.shareit.user.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.shareit.user.model.User;
+
+import java.util.Optional;
+
+public interface UserRepository extends JpaRepository {
+ Optional findByEmail(String email);
+}
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 905c72c..e5d966b 100644
--- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java
+++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java
@@ -8,10 +8,9 @@
import ru.practicum.shareit.user.dto.UserDto;
import ru.practicum.shareit.user.dto.mapper.UserMapper;
import ru.practicum.shareit.user.model.User;
-import ru.practicum.shareit.user.dao.UserStorage;
+import ru.practicum.shareit.user.repository.UserRepository;
import java.util.List;
-import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
@@ -19,23 +18,19 @@
@Service
public class UserServiceImpl implements UserService {
- private final UserStorage userStorage;
+ private final UserRepository userRepository;
@Override
public List findAllUsers() {
- return userStorage.findAllUsers().stream()
+ return userRepository.findAll().stream()
.map(UserMapper::toUserDto)
.collect(Collectors.toList());
}
@Override
public UserDto findUserById(Long id) {
- User user = userStorage.findUserById(id);
+ User user = validateUser(id);
- if (user == null) {
- log.warn("Пользователь с данным id {} не найден", id);
- throw new NotFoundException("Пользователь с данным id: " + id + " не найден");
- }
return UserMapper.toUserDto(user);
}
@@ -43,34 +38,43 @@ public UserDto findUserById(Long id) {
public UserDto createUser(UserDto userDto) {
validateEmail(null, userDto);
- return UserMapper.toUserDto(userStorage.createUser(UserMapper.toUser(userDto)));
+ return UserMapper.toUserDto(userRepository.save(UserMapper.toUser(userDto)));
}
@Override
public UserDto updateUser(Long id, UserDto updateUserDto) {
- User existingUser = userStorage.findUserById(id);
+ User existingUser = validateUser(id);
+
+ validateEmail(id, updateUserDto);
- if (existingUser == null) {
- log.warn("Пользователь с id: {} не найден", id);
- throw new NotFoundException("Пользователь с данным id " + id + " не найден.");
+ if (updateUserDto.getName() != null && !updateUserDto.getName().isBlank()) {
+ existingUser.setName(updateUserDto.getName());
}
- validateEmail(id, updateUserDto);
+ if (updateUserDto.getEmail() != null && !updateUserDto.getEmail().isBlank()) {
+ existingUser.setEmail(updateUserDto.getEmail());
+ }
- return UserMapper.toUserDto(userStorage.updateUser(UserMapper.toUser(id, updateUserDto)));
+ return UserMapper.toUserDto(userRepository.save(existingUser));
}
@Override
public void deleteUser(Long id) {
- userStorage.deleteUser(id);
+ userRepository.deleteById(id);
}
private void validateEmail(Long id, UserDto userDto) {
- if (userStorage.findAllUsers().stream()
- .anyMatch(viewedUser -> Objects.equals(viewedUser.getEmail(), userDto.getEmail())
- && !Objects.equals(viewedUser.getId(), id))) {
- log.warn("Данная электронная почта {} уже существует", userDto.getEmail());
- throw new InternalServerException("Данная электронная почта уже существует");
- }
+ userRepository.findByEmail(userDto.getEmail())
+ .ifPresent(user -> {
+ if (!user.getId().equals(id)) {
+ log.warn("Данная электронная почта уже существует");
+ throw new InternalServerException("Данная электронная почта уже существует");
+ }
+ });
+ }
+
+ private User validateUser(Long userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Пользователь с данным id: " + userId + " не найден"));
}
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index b9e5d4b..0000000
--- a/src/main/resources/application.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-spring.jpa.hibernate.ddl-auto=none
-spring.jpa.properties.hibernate.format_sql=true
-spring.sql.init.mode=always
-
-logging.level.org.springframework.orm.jpa=INFO
-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 DB
-#spring.datasource.driverClassName
-#spring.datasource.url
-#spring.datasource.username
-#spring.datasource.password
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
new file mode 100644
index 0000000..f5599d8
--- /dev/null
+++ b/src/main/resources/application.yaml
@@ -0,0 +1,35 @@
+server:
+ port: 8080
+
+spring:
+ sql:
+ init:
+ mode: always
+ jpa:
+ show-sql: true
+ hibernate:
+ ddl-auto: none
+ properties:
+ hibernate:
+ format_sql: true
+ jdbc:
+ time_zone: UTC
+ lob:
+ non_contextual_creation: true
+
+ datasource:
+ url: jdbc:postgresql://localhost:5438/shareitdb
+ username: dbuser
+ password: 12345
+ driver-class-name: org.postgresql.Driver
+
+logging:
+ level:
+ org:
+ springframework:
+ orm:
+ jpa: INFO
+ jpa.JpaTransactionManager: DEBUG
+ transaction:
+ interceptor: TRACE
+ transaction.interceptor: TRACE
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
new file mode 100644
index 0000000..fa318ff
--- /dev/null
+++ b/src/main/resources/schema.sql
@@ -0,0 +1,55 @@
+drop table IF EXISTS users CASCADE;
+drop table IF EXISTS items CASCADE;
+drop table IF EXISTS bookings CASCADE;
+drop table IF EXISTS requests CASCADE;
+drop table IF EXISTS comments CASCADE;
+
+create TABLE IF NOT EXISTS users (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ email VARCHAR(512) NOT NULL,
+ CONSTRAINT pk_user PRIMARY KEY (id),
+ CONSTRAINT UQ_USER_EMAIL UNIQUE (email)
+);
+
+create TABLE IF NOT EXISTS items (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ description VARCHAR(2000),
+ available BOOLEAN DEFAULT TRUE,
+ owner_id BIGINT NOT NULL,
+ CONSTRAINT pk_item PRIMARY KEY (id),
+ CONSTRAINT fk_item_owner FOREIGN KEY (owner_id) REFERENCES users(id)
+);
+
+create TABLE IF NOT EXISTS bookings (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ start_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ end_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ item_id BIGINT NOT NULL,
+ booker_id BIGINT NOT NULL,
+ status VARCHAR(50),
+ CONSTRAINT pk_booking PRIMARY KEY (id),
+ CONSTRAINT fk_booking_item FOREIGN KEY (item_id) REFERENCES items(id),
+ CONSTRAINT fk_booking_booker FOREIGN KEY (booker_id) REFERENCES users(id)
+);
+
+create TABLE IF NOT EXISTS requests (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ description VARCHAR(2000),
+ requestor_id BIGINT NOT NULL,
+ CONSTRAINT pk_request PRIMARY KEY (id),
+ CONSTRAINT fk_request_requestor FOREIGN KEY (requestor_id) REFERENCES users(id)
+);
+
+create TABLE IF NOT EXISTS comments (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
+ text VARCHAR(2000),
+ item_id BIGINT NOT NULL,
+ author_id BIGINT NOT NULL,
+ created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ CONSTRAINT pk_comment PRIMARY KEY (id),
+ CONSTRAINT fk_comment_item FOREIGN KEY (item_id) REFERENCES items(id),
+ CONSTRAINT fk_comment_author FOREIGN KEY (author_id) REFERENCES users(id)
+);
+
diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/src/test/java/ru/practicum/shareit/ShareItTests.java
index 4d79052..6dd7261 100644
--- a/src/test/java/ru/practicum/shareit/ShareItTests.java
+++ b/src/test/java/ru/practicum/shareit/ShareItTests.java
@@ -1,3 +1,4 @@
+/*
package ru.practicum.shareit;
import org.junit.jupiter.api.Test;
@@ -11,3 +12,4 @@ void contextLoads() {
}
}
+*/