diff --git a/pom.xml b/pom.xml
index 2db888c..1546b4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,247 +1,270 @@
- 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.postgresql
- postgresql
- runtime
-
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
-
- org.projectlombok
- lombok
- true
-
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
-
- com.h2database
- h2
- test
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
+
+ org.postgresql
+ postgresql
+ runtime
+
-
-
-
- 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
-
-
-
-
-
+
+ org.projectlombok
+ lombok
+ 1.18.38
+ provided
+
-
+
+ com.h2database
+ h2
+ test
+
+
+
+ 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-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.38
+
+
+
+
+
+
+
+
+
+ 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/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java
new file mode 100644
index 0000000..d107637
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java
@@ -0,0 +1,60 @@
+package ru.practicum.shareit.booking;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingNewDto;
+import ru.practicum.shareit.booking.model.BookingState;
+
+import java.util.Collection;
+
+/**
+ * TODO Sprint add-bookings.
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping(path = "/bookings")
+public class BookingController {
+
+ private final BookingService bookingService;
+
+ @GetMapping("/{bookingId}")
+ public BookingDto findById(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable Long bookingId) {
+ log.info("Запрос на получение бронирования с Id:{} от пользователя с Id:{}", bookingId, userId);
+ return bookingService.findById(userId, bookingId);
+ }
+
+ @PostMapping
+ public BookingDto create(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestBody BookingNewDto bookingNewDto) {
+ log.info("Запрос на создание бронирования с Id:{} от пользователя с Id:{}", userId, bookingNewDto.getItemId());
+ return bookingService.create(userId, bookingNewDto);
+ }
+
+ @PatchMapping("/{bookingId}")
+ public BookingDto approve(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable Long bookingId,
+ @RequestParam(name = "approved") Boolean isApproved) {
+ log.info("Запрос на подтверждение или отклонение бронирования с Id:{}", bookingId);
+ return bookingService.approve(userId, bookingId, isApproved);
+ }
+
+ @GetMapping("/owner")
+ public Collection findByOwnerId(@RequestHeader("X-Sharer-User-Id") Long ownerId,
+ @RequestParam(name = "state", defaultValue = "ALL")
+ BookingState bookingState) {
+ log.info("Запрос на получение списка бронирований владельца с id:{}", ownerId);
+ return bookingService.findByOwnerId(ownerId, bookingState);
+ }
+
+ @GetMapping
+ public Collection findByBookerId(@RequestHeader("X-Sharer-User-Id") Long bookerId,
+ @RequestParam(name = "state", defaultValue = "ALL")
+ BookingState bookingState) {
+ log.info("Запрос на получение списка бронирований пользователя с id:{}", bookerId);
+ return bookingService.findByBookerId(bookerId, bookingState);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingService.java b/src/main/java/ru/practicum/shareit/booking/BookingService.java
new file mode 100644
index 0000000..4edab6b
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/BookingService.java
@@ -0,0 +1,20 @@
+package ru.practicum.shareit.booking;
+
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingNewDto;
+import ru.practicum.shareit.booking.model.BookingState;
+
+import java.util.Collection;
+
+public interface BookingService {
+
+ BookingDto findById(Long userId, Long bookingId);
+
+ BookingDto create(Long userId, BookingNewDto bookingNewDto);
+
+ BookingDto approve(Long userId, Long bookingId, Boolean isApproved);
+
+ Collection findByOwnerId(Long ownerId, BookingState bookingState);
+
+ Collection findByBookerId(Long bookerId, BookingState bookingState);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java
new file mode 100644
index 0000000..26c2589
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java
@@ -0,0 +1,218 @@
+package ru.practicum.shareit.booking;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.booking.dao.BookingRepository;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingNewDto;
+import ru.practicum.shareit.booking.mapper.BookingMapper;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.model.BookingState;
+import ru.practicum.shareit.booking.model.BookingStatus;
+import ru.practicum.shareit.booking.utils.BookingValidate;
+import ru.practicum.shareit.exception.*;
+import ru.practicum.shareit.exception.ValidationException;
+import ru.practicum.shareit.item.dao.ItemRepository;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.dao.UserRepository;
+import ru.practicum.shareit.user.model.User;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class BookingServiceImpl implements BookingService {
+
+ private final BookingRepository bookingRepository;
+ private final ItemRepository itemRepository;
+ private final UserRepository userRepository;
+
+ // Получение бронирования по id
+ @Override
+ public BookingDto findById(Long userId, Long bookingId) {
+ if (userId == null || userId <= 0) {
+ throw new ValidationException("Некорректный id пользователя");
+ }
+
+ if (bookingId == null || bookingId <= 0) {
+ throw new ValidationException("Некорректный id бронирования");
+ }
+
+ Booking booking = bookingRepository.findById(bookingId)
+ .orElseThrow(() -> new NotFoundException("Бронирование не найдено"));
+
+ // Проверяем права доступа
+ if (!booking.getBooker().getId().equals(userId) &&
+ !booking.getItem().getOwner().getId().equals(userId)) {
+ throw new ForbiddenException("Доступ к бронированию запрещен");
+ }
+
+ return BookingMapper.mapToDto(booking);
+ }
+
+ // Добавление бронирования
+ @Override
+ public BookingDto create(Long userId, BookingNewDto bookingNewDto) {
+ if (userId == null || userId <= 0) {
+ throw new ValidationException("Некорректный ID пользователя");
+ }
+
+ if (bookingNewDto == null) {
+ throw new ValidationException("Бронирование не может быть null");
+ }
+
+ User booker = userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Пользователь не найден"));
+ Item item = itemRepository.findById(bookingNewDto.getItemId())
+ .orElseThrow(() -> new NotFoundException("Вещь с id:" + bookingNewDto.getItemId() + "не найдена"));
+
+ if (item.getOwner().getId().equals(userId)) {
+ throw new ForbiddenException("Владелец не может бронировать свою вещь");
+ }
+
+ // Проверяем доступность вещи
+ if (!item.getAvailable()) {
+ throw new ValidationException("Вещь недоступна для бронирования");
+ }
+
+ Booking booking = BookingMapper.mapToBooking(bookingNewDto);
+ booking.setItem(item);
+ booking.setBooker(booker);
+ booking.setStatus(BookingStatus.WAITING);
+ BookingValidate.validateAll(booking);
+
+ Booking createdBooking = bookingRepository.save(booking);
+ return BookingMapper.mapToDto(createdBooking);
+ }
+
+ // Подтверждение бронирования
+ @Override
+ public BookingDto approve(Long userId, Long bookingId, Boolean isApproved) {
+ if (userId == null || userId <= 0) {
+ throw new ValidationException("Некорректный ID пользователя");
+ }
+
+ if (bookingId == null || bookingId <= 0) {
+ throw new ValidationException("Некорректный ID бронирования");
+ }
+
+ if (isApproved == null) {
+ throw new ValidationException("Статус подтверждения не может быть null");
+ }
+
+ Booking booking = bookingRepository.findById(bookingId)
+ .orElseThrow(() -> new NotFoundException("Бронирование не найдено"));
+
+ // Проверка прав доступа
+ if (!booking.getItem().getOwner().getId().equals(userId)) {
+ log.warn("Пользователь ID: {} не является владельцем вещи ID: {}",
+ userId, booking.getItem().getId());
+ throw new ForbiddenException("Только владелец вещи может подтверждать бронирование");
+ }
+
+ // Проверка текущего статуса
+ if (!BookingStatus.WAITING.equals(booking.getStatus())) {
+ log.warn("Попытка изменить статус бронирования Id: {} с текущим статусом: {}",
+ bookingId, booking.getStatus());
+ throw new ValidationException("Бронирование уже обработано");
+ }
+
+ // Установка нового статуса
+ BookingStatus newStatus = isApproved ? BookingStatus.APPROVED : BookingStatus.REJECTED;
+ booking.setStatus(newStatus);
+
+ Booking updatedBooking = bookingRepository.save(booking);
+ log.info("Бронирование Id: {} обновлено со статусом: {}", bookingId, newStatus);
+
+ return BookingMapper.mapToDto(updatedBooking);
+ }
+
+ // Получение списка бронирований владельца
+ @Override
+ public Collection findByOwnerId(Long ownerId, BookingState bookingState) {
+ if (ownerId == null || ownerId <= 0) {
+ throw new ValidationException("Некорректный ID пользователя");
+ }
+
+ if (bookingState == null) {
+ throw new ValidationException("Статус бронирования не может быть null");
+ }
+
+ userRepository.findById(ownerId)
+ .orElseThrow(() -> new NotFoundException("Пользователь с id:" + ownerId + " не найден"));
+
+ Collection bookings;
+ switch (bookingState) {
+ case ALL:
+ bookings = bookingRepository.findByItemOwnerId(ownerId);
+ break;
+ case CURRENT:
+ bookings = bookingRepository.findByOwnerIdStateCurrent(ownerId);
+ break;
+ case PAST:
+ bookings = bookingRepository.findByOwnerIdStatePast(ownerId);
+ break;
+ case FUTURE:
+ bookings = bookingRepository.findByOwnerIdStateFuture(ownerId);
+ break;
+ case WAITING:
+ bookings = bookingRepository.findByItemOwnerIdAndStatus(ownerId, BookingStatus.WAITING);
+ break;
+ case REJECTED:
+ bookings = bookingRepository.findByItemOwnerIdAndStatus(ownerId, BookingStatus.REJECTED);
+ break;
+ default:
+ throw new ValidationException("Неизвестное состояние бронирования");
+ }
+
+ return bookings.stream()
+ .map(BookingMapper::mapToDto)
+ .collect(Collectors.toList());
+ }
+
+ // Получение списка бронирований пользователя
+ @Override
+ public Collection findByBookerId(Long bookerId, BookingState bookingState) {
+ if (bookerId == null || bookerId <= 0) {
+ throw new ValidationException("Некорректный ID пользователя");
+ }
+
+ if (bookingState == null) {
+ throw new ValidationException("Статус бронирования не может быть null");
+ }
+
+ userRepository.findById(bookerId)
+ .orElseThrow(() -> new NotFoundException("Пользователь с id:" + bookerId + " не найден"));
+
+ Collection bookings;
+ switch (bookingState) {
+ case ALL:
+ bookings = bookingRepository.findByBookerId(bookerId);
+ break;
+ case CURRENT:
+ bookings = bookingRepository.findByBookerIdStateCurrent(bookerId);
+ break;
+ case PAST:
+ bookings = bookingRepository.findByOwnerIdStatePast(bookerId);
+ break;
+ case FUTURE:
+ bookings = bookingRepository.findByBookerIdStateFuture(bookerId);
+ break;
+ case WAITING:
+ bookings = bookingRepository.findByBookerIdAndStatus(bookerId, BookingStatus.WAITING);
+ break;
+ case REJECTED:
+ bookings = bookingRepository.findByBookerIdAndStatus(bookerId, BookingStatus.REJECTED);
+ break;
+ default:
+ throw new ValidationException("Неизвестное состояние бронирования");
+ }
+
+ return bookings.stream()
+ .map(BookingMapper::mapToDto)
+ .collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java
deleted file mode 100644
index 427a4b6..0000000
--- a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package ru.practicum.shareit.booking.controller;
-
-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/dao/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java
new file mode 100644
index 0000000..6cca226
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java
@@ -0,0 +1,81 @@
+package ru.practicum.shareit.booking.dao;
+
+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.BookingStatus;
+
+import java.util.Collection;
+
+public interface BookingRepository extends JpaRepository {
+
+ Collection findByBookerId(Long bookerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.booker.id = :bookerId " +
+ "AND b.start <= CURRENT_TIMESTAMP " +
+ "AND b.end >= CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED'")
+ Collection findByBookerIdStateCurrent(Long bookerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.booker.id = :bookerId " +
+ "AND b.end < CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED'")
+ Collection findByBookerIdStatePast(Long bookerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.booker.id = :bookerId " +
+ "AND b.start > CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED'")
+ Collection findByBookerIdStateFuture(Long bookerId);
+
+ Collection findByBookerIdAndStatus(Long bookerId, BookingStatus status);
+
+ Collection findByItemOwnerId(Long ownerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.item.owner.id = :ownerId " +
+ "AND b.start <= CURRENT_TIMESTAMP " +
+ "AND b.end >= CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED'")
+ Collection findByOwnerIdStateCurrent(Long ownerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.item.owner.id = :ownerId " +
+ "AND b.end < CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED'")
+ Collection findByOwnerIdStatePast(Long ownerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.item.owner.id = :ownerId " +
+ "AND b.start > CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED'")
+ Collection findByOwnerIdStateFuture(Long ownerId);
+
+ Collection findByItemOwnerIdAndStatus(Long ownerId, BookingStatus status);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.item.id = :itemId " +
+ "AND b.item.owner.id = :ownerId " +
+ "AND b.start <= CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED' " +
+ "ORDER BY b.start DESC")
+ Collection findByOwnerIdLastBookingsForItem(Long itemId, Long ownerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.item.id = :itemId " +
+ "AND b.item.owner.id = :ownerId " +
+ "AND b.start >= CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED' " +
+ "ORDER BY b.start ASC")
+ Collection findByOwnerIdNextBookingsForItem(Long itemId, Long ownerId);
+
+ @Query("SELECT b FROM Booking b " +
+ "WHERE b.booker.id = :bookerId " +
+ "AND b.item.id = :itemId " +
+ "AND b.end <= CURRENT_TIMESTAMP " +
+ "AND b.status = 'APPROVED' " +
+ "ORDER BY b.end DESC")
+ Collection findByBookerIdLastBookingsForItem(Long bookerId, Long itemId);
+}
\ 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
index 861de9e..bf2e2e1 100644
--- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
@@ -1,7 +1,34 @@
package ru.practicum.shareit.booking.dto;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import ru.practicum.shareit.booking.model.BookingStatus;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.user.dto.UserDto;
+
+import java.time.Instant;
+
/**
* TODO Sprint add-bookings.
*/
+@Getter
+@Setter
+@Builder(toBuilder = true)
public class BookingDto {
-}
+
+ private Long id;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC")
+ private Instant start;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC")
+ private Instant end;
+
+ private ItemDto item;
+
+ private UserDto booker;
+
+ private BookingStatus status;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java
new file mode 100644
index 0000000..3d70ac2
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingNewDto.java
@@ -0,0 +1,22 @@
+package ru.practicum.shareit.booking.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.*;
+
+import java.time.Instant;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder(toBuilder = true)
+public class BookingNewDto {
+
+ private Long itemId;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC")
+ private Instant start;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC")
+ private Instant end;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java
new file mode 100644
index 0000000..5834af5
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.booking.mapper;
+
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.dto.BookingNewDto;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.booking.model.BookingStatus;
+import ru.practicum.shareit.item.mapper.ItemMapper;
+import ru.practicum.shareit.user.mapper.UserMapper;
+
+public class BookingMapper {
+
+ // Преобразование Booking в DTO
+ public static BookingDto mapToDto(Booking booking) {
+ return BookingDto.builder()
+ .id(booking.getId())
+ .start(booking.getStart())
+ .end(booking.getEnd())
+ .item(ItemMapper.mapToDto(booking.getItem()))
+ .booker(UserMapper.mapToDto(booking.getBooker()))
+ .status(booking.getStatus())
+ .build();
+ }
+
+ // Преобразование BookingNewDto в Booking
+ public static Booking mapToBooking(BookingNewDto bookingNewDto) {
+ return Booking.builder()
+ .start(bookingNewDto.getStart())
+ .end(bookingNewDto.getEnd())
+ .status(BookingStatus.WAITING)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java
index 71f189c..e6d2dea 100644
--- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java
+++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java
@@ -1,7 +1,56 @@
package ru.practicum.shareit.booking.model;
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.Instant;
+
/**
* TODO Sprint add-bookings.
*/
+@Entity
+@Table(name = "bookings", schema = "public")
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder(toBuilder = true)
public class Booking {
-}
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "start_date", nullable = false)
+ private Instant start;
+
+ @Column(name = "end_date", nullable = false)
+ private Instant 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)
+ @Column(name = "status", nullable = false)
+ private BookingStatus status;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Booking)) return false;
+ return id != null && id.equals(((Booking) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingState.java b/src/main/java/ru/practicum/shareit/booking/model/BookingState.java
new file mode 100644
index 0000000..3e25892
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/model/BookingState.java
@@ -0,0 +1,5 @@
+package ru.practicum.shareit.booking.model;
+
+public enum BookingState {
+ ALL, CURRENT, PAST, FUTURE, WAITING, REJECTED
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java b/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java
new file mode 100644
index 0000000..14f2621
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java
@@ -0,0 +1,5 @@
+package ru.practicum.shareit.booking.model;
+
+public enum BookingStatus {
+ WAITING, APPROVED, REJECTED, CANCELED;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java b/src/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java
new file mode 100644
index 0000000..6b77d98
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/booking/utils/BookingValidate.java
@@ -0,0 +1,65 @@
+package ru.practicum.shareit.booking.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.exception.ValidationException;
+
+import java.time.Instant;
+
+/**
+ * Утилитарный класс для валидации данных сущности Booking
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class BookingValidate {
+
+ // Валидация даты начала бронирования
+ public static void validateForStartBooking(Booking booking) {
+ validateNotNull(booking);
+ if (booking.getStart().isBefore(Instant.now())) {
+ log.warn("Задана некорректная дата начала бронирования");
+ throw new ValidationException("Дата начала бронирования не может быть в прошлом");
+ }
+ }
+
+ // Валидация даты окончания бронирования
+ public static void validateForEndBooking(Booking booking) {
+ validateNotNull(booking);
+ if (booking.getEnd().isBefore(Instant.now())) {
+ log.warn("Задана некорректная дата окончания бронирования");
+ throw new ValidationException("Дата окончания бронирования не может быть в прошлом");
+ }
+ }
+
+ // Валидация корректности временного интервала между началом и окончанием бронирования
+ public static void validateTimeInterval(Booking booking) {
+ validateNotNull(booking);
+ if (booking.getEnd().isBefore(booking.getStart()) || booking.getEnd().equals(booking.getStart())) {
+ log.warn("Задан некорректный интервал между началом и окончанием бронирования");
+ throw new ValidationException("Дата окончания бронирования должна быть позже даты начала");
+ }
+ }
+
+ // Комплексная валидация для Booking
+ public static void validateAll(Booking booking) {
+ validateNotNull(booking);
+ validateForStartBooking(booking);
+ validateForEndBooking(booking);
+ validateTimeInterval(booking);
+ }
+
+ // Вспомогательный метод проверки бронирования на null
+ private static void validateNotNull(Booking booking) {
+ if (booking == null) {
+ log.error("Бронирование не может быть null");
+ throw new ValidationException("Бронирование не может быть null");
+ }
+
+ if (booking.getStart() == null || booking.getEnd() == null) {
+ log.error("Некорректная дата начала или дата окончания бронирования");
+ throw new ValidationException("Дата начала или дата окончания не может быть null");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java
index 736d166..0102bec 100644
--- a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java
+++ b/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java
@@ -1,9 +1,9 @@
package ru.practicum.shareit.exception;
-import lombok.Getter;
+import lombok.Data;
import lombok.RequiredArgsConstructor;
-@Getter
+@Data
@RequiredArgsConstructor
public class ErrorResponse {
private final String error;
diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java
new file mode 100644
index 0000000..73c9b62
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/ItemController.java
@@ -0,0 +1,76 @@
+package ru.practicum.shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.shareit.item.comment.dto.CommentDto;
+import ru.practicum.shareit.item.comment.dto.CommentNewDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemFullDto;
+import ru.practicum.shareit.item.dto.ItemNewDto;
+
+import java.util.Collection;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/items")
+public class ItemController {
+
+ private final ItemService itemService;
+
+ @GetMapping
+ public Collection findAllByUserId(@RequestHeader("X-Sharer-User-Id") Long userId) {
+ log.info("Запрос на получение всех вещей пользователя c Id:{}", userId);
+ return itemService.findAllByUserId(userId);
+ }
+
+ @GetMapping("/{itemId}")
+ public ItemFullDto findById(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable Long itemId) {
+ log.info("Запрос на получение вещи с Id:{}", itemId);
+ return itemService.findById(userId, itemId);
+ }
+
+ @GetMapping("/search")
+ public Collection searchByText(@RequestParam("text") String searchText) {
+ log.info("Запрос на поиск вещи в названии или в описании");
+ return itemService.searchByText(searchText);
+ }
+
+ @PostMapping
+ public ItemDto create(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @RequestBody ItemNewDto newItem) {
+ log.info("Запрос на добавление новой вещи пользователем с Id:{}", userId);
+ return itemService.create(userId, newItem);
+ }
+
+ @PatchMapping("/{itemId}")
+ public ItemDto update(@RequestHeader("X-Sharer-User-Id") Long ownerId,
+ @PathVariable Long itemId,
+ @RequestBody ItemNewDto updatedItem) {
+ log.info("Запрос на обновление вещи с Id:{} пользователем с Id:{}", itemId, ownerId);
+ return itemService.update(ownerId, itemId, updatedItem);
+ }
+
+ @DeleteMapping("/{itemId}")
+ public void delete(@RequestHeader("X-Sharer-User-Id") Long ownerId,
+ @PathVariable Long itemId) {
+ log.info("Запрос на удаление вещи с Id:{} пользователем с Id:{}", itemId, ownerId);
+ itemService.delete(ownerId, itemId);
+ }
+
+ @PostMapping("/{itemId}/comment")
+ public CommentDto addComment(@RequestHeader("X-Sharer-User-Id") Long userId,
+ @PathVariable Long itemId,
+ @RequestBody CommentNewDto comment) {
+ log.info("Запрос на добавление комментария вещи с Id:{} пользователем с Id:{}", itemId, userId);
+ return itemService.addComment(userId, itemId, comment);
+ }
+
+ @GetMapping("/{itemId}/comment")
+ public Collection findAllCommentsByItemId(@PathVariable Long itemId) {
+ log.info("Запрос на получение всех комментариев к вещи с id:{}", itemId);
+ return itemService.findAllCommentsByItemId(itemId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/ItemService.java b/src/main/java/ru/practicum/shareit/item/ItemService.java
new file mode 100644
index 0000000..eb9d1bd
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/ItemService.java
@@ -0,0 +1,27 @@
+package ru.practicum.shareit.item;
+
+import ru.practicum.shareit.item.comment.dto.CommentDto;
+import ru.practicum.shareit.item.comment.dto.CommentNewDto;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemFullDto;
+import ru.practicum.shareit.item.dto.ItemNewDto;
+
+import java.util.Collection;
+
+public interface ItemService {
+ Collection findAllByUserId(Long userId);
+
+ ItemFullDto findById(Long userId, Long itemId);
+
+ Collection searchByText(String searchText);
+
+ ItemDto create(Long ownerId, ItemNewDto newItem);
+
+ ItemDto update(Long ownerId, Long itemId, ItemNewDto updatedItem);
+
+ void delete(Long ownerID, Long itemId);
+
+ CommentDto addComment(Long userId, Long itemId, CommentNewDto comment);
+
+ Collection findAllCommentsByItemId(Long itemId);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java
new file mode 100644
index 0000000..a6cdb97
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java
@@ -0,0 +1,198 @@
+package ru.practicum.shareit.item;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import ru.practicum.shareit.booking.dao.BookingRepository;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.booking.mapper.BookingMapper;
+import ru.practicum.shareit.booking.model.Booking;
+import ru.practicum.shareit.exception.*;
+import ru.practicum.shareit.item.comment.dao.CommentRepository;
+import ru.practicum.shareit.item.comment.dto.CommentDto;
+import ru.practicum.shareit.item.comment.dto.CommentNewDto;
+import ru.practicum.shareit.item.comment.mapper.CommentMapper;
+import ru.practicum.shareit.item.comment.model.Comment;
+import ru.practicum.shareit.item.dao.ItemRepository;
+import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemFullDto;
+import ru.practicum.shareit.item.dto.ItemNewDto;
+import ru.practicum.shareit.item.mapper.ItemMapper;
+import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.item.utils.ItemValidate;
+import ru.practicum.shareit.user.dao.UserRepository;
+import ru.practicum.shareit.user.model.User;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ItemServiceImpl implements ItemService {
+
+ private final ItemRepository itemRepository;
+ private final UserRepository userRepository;
+ private final BookingRepository bookingRepository;
+ private final CommentRepository commentRepository;
+
+ // Получение всех вещей пользователя
+ @Override
+ public Collection findAllByUserId(Long userId) {
+ return itemRepository.findAllByOwnerId(userId)
+ .stream()
+ .map(item -> {
+ BookingDto lastBooking = findLastBooking(item.getId(), userId);
+ BookingDto nextBooking = findNextBooking(item.getId(), userId);
+ List comments = findCommentsForItem(item.getId());
+ return ItemMapper.mapToFullDto(item, lastBooking, nextBooking, comments);
+ })
+ .collect(Collectors.toList());
+ }
+
+ // Получение вещи по id
+ @Override
+ public ItemFullDto findById(Long userId, Long itemId) {
+ Item item = itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Вещь не найдена"));
+
+ BookingDto lastBooking = findLastBooking(itemId, userId);
+ BookingDto nextBooking = findNextBooking(itemId, userId);
+ List comments = findCommentsForItem(itemId);
+
+ return ItemMapper.mapToFullDto(item, lastBooking, nextBooking, comments);
+ }
+
+ // Поиск вещи по тексту в названии или описании
+ @Override
+ public Collection searchByText(String searchText) {
+ if (searchText == null || searchText.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return itemRepository.searchByText(searchText.toLowerCase())
+ .stream()
+ .map(ItemMapper::mapToDto)
+ .collect(Collectors.toList());
+ }
+
+ // Добавление вещи
+ @Override
+ public ItemDto create(Long ownerId, ItemNewDto itemNewDto) {
+ ItemValidate.validateForCreate(itemNewDto);
+ User owner = userRepository.findById(ownerId)
+ .orElseThrow(() -> new NotFoundException("Пользователь с ID" + ownerId + " не найден"));
+ Item item = ItemMapper.mapToItem(itemNewDto, owner);
+ Item createdItem = itemRepository.save(item);
+ return ItemMapper.mapToDto(createdItem);
+ }
+
+ // Обновление вещи
+ @Override
+ public ItemDto update(Long ownerId, Long itemId, ItemNewDto itemNewDto) {
+ ItemValidate.validateForUpdate(itemNewDto);
+
+ Item existingItem = itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + "не найдена"));
+
+ if (!existingItem.getOwner().getId().equals(ownerId)) {
+ log.warn("Попытка изменения чужой вещи");
+ throw new ForbiddenException("Пользователь не является владельцем вещи");
+ }
+
+ if (itemNewDto.getName() != null) {
+ existingItem.setName(itemNewDto.getName());
+ }
+ if (itemNewDto.getDescription() != null) {
+ existingItem.setDescription(itemNewDto.getDescription());
+ }
+ if (itemNewDto.getAvailable() != null) {
+ existingItem.setAvailable(itemNewDto.getAvailable());
+ }
+
+ Item updatedItem = itemRepository.save(existingItem);
+ return ItemMapper.mapToDto(updatedItem);
+ }
+
+ // Удаление вещи
+ @Override
+ public void delete(Long ownerId, Long itemId) {
+ Item item = itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + "не найдена"));
+
+ if (!item.getOwner().getId().equals(ownerId)) {
+ log.warn("Попытка удаления чужой вещи. Пользователь ID: {}, Владелец вещи ID: {}",
+ ownerId, item.getOwner().getId());
+ throw new ForbiddenException("Недостаточно прав для удаления вещи");
+ }
+
+ itemRepository.deleteById(itemId);
+ }
+
+ // Добавление комментариев вещи
+ @Override
+ public CommentDto addComment(Long userId, Long itemId, CommentNewDto commentNewDto) {
+ User author = userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден"));
+
+ Item item = itemRepository.findById(itemId)
+ .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + "не найдена"));
+
+ Collection bookings = bookingRepository.findByBookerIdLastBookingsForItem(userId, itemId);
+ if (bookings.isEmpty()) {
+ log.error("Пользователь с id:{} не бронировал вещь с id:{}", userId, itemId);
+ throw new ValidationException("Нельзя оставить комментарий, если пользователь не бронировал вещь");
+ }
+
+ Comment comment = Comment.builder()
+ .text(commentNewDto.getText())
+ .item(item)
+ .author(author)
+ .created(Instant.now())
+ .build();
+ Comment savedComment = commentRepository.save(comment);
+ return CommentMapper.mapToDto(savedComment);
+ }
+
+ // Получение всех комментариев вещи
+ @Override
+ public Collection findAllCommentsByItemId(Long itemId) {
+ if (!itemRepository.existsById(itemId)) {
+ throw new NotFoundException("Вещь с ID " + itemId + " не найдена");
+ }
+
+ return commentRepository.findByItemId(itemId)
+ .stream()
+ .map(CommentMapper::mapToDto)
+ .collect(Collectors.toList());
+ }
+
+ // Вспомогательный метод для поиска последнего бронирования для вещи
+ private BookingDto findLastBooking(Long itemId, Long userId) {
+ Collection lastBookings = bookingRepository.findByOwnerIdLastBookingsForItem(itemId, userId);
+ return lastBookings.stream()
+ .findFirst()
+ .map(BookingMapper::mapToDto)
+ .orElse(null);
+ }
+
+ // Вспомогательный метод для поиска следующего бронирования для вещи
+ private BookingDto findNextBooking(Long itemId, Long userId) {
+ Collection nextBookings = bookingRepository.findByOwnerIdNextBookingsForItem(itemId, userId);
+ return nextBookings.stream()
+ .findFirst()
+ .map(BookingMapper::mapToDto)
+ .orElse(null);
+ }
+
+ // Вспомогательный метод для получения комментариев для вещи
+ private List findCommentsForItem(Long itemId) {
+ return commentRepository.findByItemId(itemId)
+ .stream()
+ .map(CommentMapper::mapToDto)
+ .collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java b/src/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java
new file mode 100644
index 0000000..41ebc7f
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/dao/CommentRepository.java
@@ -0,0 +1,11 @@
+package ru.practicum.shareit.item.comment.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.shareit.item.comment.model.Comment;
+
+import java.util.Collection;
+
+public interface CommentRepository extends JpaRepository {
+
+ Collection findByItemId(Long itemId);
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java
new file mode 100644
index 0000000..d1623c2
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentDto.java
@@ -0,0 +1,23 @@
+package ru.practicum.shareit.item.comment.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.Instant;
+
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class CommentDto {
+
+ private Long id;
+
+ private String text;
+
+ private String authorName;
+
+ @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", shape = JsonFormat.Shape.STRING, timezone = "UTC")
+ private Instant created;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java
new file mode 100644
index 0000000..9ae58cb
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/dto/CommentNewDto.java
@@ -0,0 +1,13 @@
+package ru.practicum.shareit.item.comment.dto;
+
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder(toBuilder = true)
+public class CommentNewDto {
+
+ private String text;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java b/src/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java
new file mode 100644
index 0000000..b2ad932
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/mapper/CommentMapper.java
@@ -0,0 +1,32 @@
+package ru.practicum.shareit.item.comment.mapper;
+
+import ru.practicum.shareit.item.comment.dto.CommentDto;
+import ru.practicum.shareit.item.comment.dto.CommentNewDto;
+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.Instant;
+
+public class CommentMapper {
+
+ // Преобразование Comment в Dto
+ public static CommentDto mapToDto(Comment comment) {
+ return CommentDto.builder()
+ .id(comment.getId())
+ .text(comment.getText())
+ .authorName(comment.getAuthor().getName())
+ .created(comment.getCreated())
+ .build();
+ }
+
+ // Преобразование CommentNewDto в Comment
+ public static Comment mapToComment(CommentNewDto commentNewDto, Item item, User author) {
+ return Comment.builder()
+ .text(commentNewDto.getText())
+ .item(item)
+ .author(author)
+ .created(Instant.now())
+ .build();
+ }
+}
\ No newline at end of file
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..0e6e6af
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/comment/model/Comment.java
@@ -0,0 +1,55 @@
+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.Instant;
+
+@Entity
+@Table(name = "comments", schema = "public")
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder(toBuilder = true)
+public class Comment {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "text", nullable = false)
+ 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;
+
+ @Column(name = "created", nullable = false)
+ private Instant created;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof Comment)) {
+ return false;
+ }
+
+ return id != null && id.equals(((Comment) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
deleted file mode 100644
index 3fb7b92..0000000
--- a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package ru.practicum.shareit.item.controller;
-
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.*;
-import ru.practicum.shareit.item.dto.ItemDto;
-import ru.practicum.shareit.item.service.ItemService;
-
-import java.util.Collection;
-
-/**
- * TODO Sprint add-controllers.
- */
-@Slf4j
-@RestController
-@RequiredArgsConstructor
-@RequestMapping("/items")
-public class ItemController {
-
- private final ItemService itemService;
-
- @GetMapping
- public Collection findAllByUserId(@NonNull @RequestHeader("X-Sharer-User-Id") Long userId) {
- log.info("Запрос на получение всех вещей пользователя c Id:{}", userId);
- return itemService.findAllByUserId(userId);
- }
-
- @GetMapping("/{itemId}")
- public ItemDto findById(@NonNull @PathVariable Long itemId) {
- log.info("Запрос на получение вещи с Id:{}", itemId);
- return itemService.findById(itemId);
- }
-
- @GetMapping("/search")
- public Collection searchByText(@NonNull @RequestParam("text") String searchText) {
- log.info("Запрос на поиск вещи в названии или в описании");
- return itemService.searchByText(searchText);
- }
-
- @PostMapping
- public ItemDto create(@NonNull @RequestHeader("X-Sharer-User-Id") Long userId,
- @NonNull @RequestBody ItemDto newItem) {
- log.info("Запрос на добавление новой вещи пользователем с Id:{}", userId);
- return itemService.create(userId, newItem);
- }
-
- @PatchMapping("/{itemId}")
- public ItemDto update(@NonNull @RequestHeader("X-Sharer-User-Id") Long ownerId,
- @NonNull @PathVariable Long itemId,
- @NonNull @RequestBody ItemDto updatedItem) {
- log.info("Запрос на обновление вещи с Id:{} пользователем с Id:{}", itemId, ownerId);
- return itemService.update(ownerId, itemId, updatedItem);
- }
-
- @DeleteMapping("/{itemId}")
- public void delete(@NonNull @RequestHeader("X-Sharer-User-Id") Long ownerId,
- @NonNull @PathVariable Long itemId) {
- log.info("Запрос на удаление вещи с Id:{} пользователем с Id:{}", itemId, ownerId);
- itemService.delete(ownerId, itemId);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java
index fb3fb1e..e401e05 100644
--- a/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java
+++ b/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java
@@ -1,20 +1,18 @@
package ru.practicum.shareit.item.dao;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
import ru.practicum.shareit.item.model.Item;
import java.util.Collection;
-import java.util.Optional;
-public interface ItemRepository {
- Collection- findAllByUserId(Long userId);
+public interface ItemRepository extends JpaRepository
- {
- Optional
- findById(Long itemId);
+ Collection
- findAllByOwnerId(Long ownerId);
+ @Query("SELECT i FROM Item i " +
+ "WHERE (LOWER(i.name) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
+ "OR LOWER(i.description) LIKE LOWER(CONCAT('%', :searchText, '%'))) " +
+ "AND i.available = true")
Collection
- searchByText(String searchText);
-
- Item create(Item item, Long userId);
-
- Item update(Item item);
-
- void delete(Long itemId);
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemRepositoryImpl.java b/src/main/java/ru/practicum/shareit/item/dao/ItemRepositoryImpl.java
deleted file mode 100644
index ef17a63..0000000
--- a/src/main/java/ru/practicum/shareit/item/dao/ItemRepositoryImpl.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package ru.practicum.shareit.item.dao;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Repository;
-import ru.practicum.shareit.item.model.Item;
-
-import java.util.*;
-
-@Slf4j
-@Repository
-public class ItemRepositoryImpl implements ItemRepository {
-
- private final Map items = new HashMap<>();
- private final Map> userItemIndex = new HashMap<>();
- private long idCounter = 0L;
-
- @Override
- public Collection
- findAllByUserId(Long userId) {
- return items.values()
- .stream()
- .filter(item -> item.getOwnerId().equals(userId))
- .toList();
- }
-
- @Override
- public Optional
- findById(Long itemId) {
- return Optional.ofNullable(items.get(itemId));
- }
-
- @Override
- public Collection
- searchByText(String searchText) {
- String text = searchText.toLowerCase();
- return items.values()
- .stream()
- .filter(item -> (item.getName().toLowerCase().contains(text))
- || (item.getDescription().toLowerCase().contains(text))
- && (item.getAvailable().equals(true)))
- .toList();
- }
-
- @Override
- public Item create(Item item, Long userId) {
- item.setItemId(++idCounter);
- item.setOwnerId(userId);
- items.put(item.getItemId(), item);
- final List
- itemsByOwner = userItemIndex.computeIfAbsent(item.getOwnerId(), k -> new ArrayList<>());
- itemsByOwner.add(item);
- return item;
- }
-
- @Override
- public Item update(Item item) {
- items.put(item.getItemId(), item);
- return item;
- }
-
- @Override
- public void delete(Long itemId) {
- items.remove(itemId);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
index 84900c1..bd15707 100644
--- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java
@@ -1,9 +1,9 @@
package ru.practicum.shareit.item.dto;
-import lombok.Builder;
-import lombok.Data;
+import lombok.*;
-@Data
+@Getter
+@Setter
@Builder(toBuilder = true)
public class ItemDto {
private Long id;
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java
new file mode 100644
index 0000000..832f94a
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemFullDto.java
@@ -0,0 +1,24 @@
+package ru.practicum.shareit.item.dto;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.item.comment.dto.CommentDto;
+
+import java.util.List;
+
+@Getter
+@Setter
+@Builder(toBuilder = true)
+public class ItemFullDto {
+
+ private Long id;
+ private String name;
+ private String description;
+ private Boolean available;
+ private Long ownerId;
+ private BookingDto lastBooking;
+ private BookingDto nextBooking;
+ private List comments;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java
new file mode 100644
index 0000000..25c5e82
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/item/dto/ItemNewDto.java
@@ -0,0 +1,15 @@
+package ru.practicum.shareit.item.dto;
+
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder(toBuilder = true)
+public class ItemNewDto {
+
+ private String name;
+ private String description;
+ private Boolean available;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java
index dfdf6b9..38fd288 100644
--- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java
+++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java
@@ -1,29 +1,50 @@
package ru.practicum.shareit.item.mapper;
+import ru.practicum.shareit.booking.dto.BookingDto;
+import ru.practicum.shareit.item.comment.dto.CommentDto;
import ru.practicum.shareit.item.dto.ItemDto;
+import ru.practicum.shareit.item.dto.ItemFullDto;
+import ru.practicum.shareit.item.dto.ItemNewDto;
import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.user.model.User;
+
+import java.util.List;
public class ItemMapper {
// Преобразование Item в Dto
public static ItemDto mapToDto(Item item) {
return ItemDto.builder()
- .id(item.getItemId())
+ .id(item.getId())
.name(item.getName())
.description(item.getDescription())
.available(item.getAvailable())
- .ownerId(item.getOwnerId())
+ .ownerId(item.getOwner().getId())
.build();
}
- // Преобразование ItemDto в Item
- public static Item mapToItem(ItemDto itemDto) {
+ // Преобразование ItemNewDto в Item
+ public static Item mapToItem(ItemNewDto itemNewDto, User owner) {
return Item.builder()
- .itemId(itemDto.getId())
- .name(itemDto.getName())
- .description(itemDto.getDescription())
- .available(itemDto.getAvailable())
- .ownerId(itemDto.getOwnerId())
+ .name(itemNewDto.getName())
+ .description(itemNewDto.getDescription())
+ .available(itemNewDto.getAvailable())
+ .owner(owner)
+ .build();
+ }
+
+ // Преобразование Item в ItemFullDto
+ public static ItemFullDto mapToFullDto(Item item, BookingDto lastBooking,
+ BookingDto nextBooking, List comments) {
+ return ItemFullDto.builder()
+ .id(item.getId())
+ .name(item.getName())
+ .description(item.getDescription())
+ .available(item.getAvailable())
+ .ownerId(item.getOwner().getId())
+ .lastBooking(lastBooking)
+ .nextBooking(nextBooking)
+ .comments(comments)
.build();
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/src/main/java/ru/practicum/shareit/item/model/Item.java
index 640eee5..8f96f2a 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,52 @@
package ru.practicum.shareit.item.model;
-import lombok.Builder;
-import lombok.Data;
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.shareit.user.model.User;
-/**
- * TODO Sprint add-controllers.
- */
-@Data
+
+@Getter
+@Setter
+@ToString
+@Entity
+@Table(name = "items")
+@NoArgsConstructor
+@AllArgsConstructor
@Builder(toBuilder = true)
public class Item {
- private Long itemId;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "name", nullable = false)
private String name;
+
+ @Column(name = "description")
private String description;
+
+ @Column(name = "is_available", nullable = false)
private Boolean available;
- private Long ownerId;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "owner_id", nullable = false)
+ private User owner;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof Item)) {
+ return false;
+ }
+
+ return id != null && id.equals(((Item) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java
deleted file mode 100644
index cac40a9..0000000
--- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package ru.practicum.shareit.item.service;
-
-import ru.practicum.shareit.item.dto.ItemDto;
-
-import java.util.Collection;
-
-public interface ItemService {
- Collection findAllByUserId(Long userId);
-
- ItemDto findById(Long itemId);
-
- Collection searchByText(String searchText);
-
- ItemDto create(Long ownerId, ItemDto newItem);
-
- ItemDto update(Long ownerId, Long itemId, ItemDto updatedItem);
-
- void delete(Long ownerID, Long itemId);
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java
deleted file mode 100644
index 146ce24..0000000
--- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package ru.practicum.shareit.item.service;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import ru.practicum.shareit.exception.*;
-import ru.practicum.shareit.item.dao.ItemRepository;
-import ru.practicum.shareit.item.dto.ItemDto;
-import ru.practicum.shareit.item.mapper.ItemMapper;
-import ru.practicum.shareit.item.model.Item;
-import ru.practicum.shareit.item.utils.ItemValidate;
-import ru.practicum.shareit.user.dto.UserDto;
-import ru.practicum.shareit.user.service.UserService;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.stream.Collectors;
-
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class ItemServiceImpl implements ItemService {
-
- private final ItemRepository itemRepository;
- private final UserService userService;
-
- // Получение всех вещей пользователя
- @Override
- public Collection findAllByUserId(Long userId) {
- return itemRepository.findAllByUserId(userId)
- .stream()
- .map(ItemMapper::mapToDto)
- .toList();
- }
-
- // Получение вещи по id
- @Override
- public ItemDto findById(Long itemId) {
- return itemRepository.findById(itemId)
- .map(ItemMapper::mapToDto)
- .orElseThrow(() -> new NotFoundException("Вещь не найдена"));
- }
-
- // Поиск вещи по тексту в названии или описании
- @Override
- public Collection searchByText(String searchText) {
- if (searchText == null || searchText.trim().isEmpty()) {
- return Collections.emptyList();
- }
-
- return itemRepository.searchByText(searchText.toLowerCase())
- .stream()
- .filter(item -> item.getAvailable() != null && item.getAvailable())
- .map(ItemMapper::mapToDto)
- .collect(Collectors.toList());
- }
-
- // Добавление вещи
- @Override
- public ItemDto create(Long ownerId, ItemDto itemDto) {
- UserDto user = userService.findById(ownerId);
-
- if (user == null) {
- throw new NotFoundException("Пользователь с ID" + ownerId + " не найден");
- }
-
- Item item = ItemMapper.mapToItem(itemDto);
- item.setOwnerId(ownerId);
- ItemValidate.validateForCreate(item);
-
- Item createdItem = itemRepository.create(item, ownerId);
- return ItemMapper.mapToDto(createdItem);
- }
-
- // Обновление вещи
- @Override
- public ItemDto update(Long ownerId, Long itemId, ItemDto itemDto) {
- userService.findById(ownerId);
-
- Item existingItem = itemRepository.findById(itemId)
- .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + "не найдена"));
-
- if (!existingItem.getOwnerId().equals(ownerId)) {
- throw new ForbiddenException("Пользователь не является владельцем вещи");
- }
-
- if (itemDto.getName() != null) {
- existingItem.setName(itemDto.getName());
- }
- if (itemDto.getDescription() != null) {
- existingItem.setDescription(itemDto.getDescription());
- }
- if (itemDto.getAvailable() != null) {
- existingItem.setAvailable(itemDto.getAvailable());
- }
-
- ItemValidate.validateForUpdate(existingItem);
- Item updatedItem = itemRepository.update(existingItem);
- return ItemMapper.mapToDto(updatedItem);
- }
-
- // Удаление вещи
- @Override
- public void delete(Long ownerId, Long itemId) {
- itemRepository.delete(itemId);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java b/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java
index 5b8d629..8412352 100644
--- a/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java
+++ b/src/main/java/ru/practicum/shareit/item/utils/ItemValidate.java
@@ -4,7 +4,7 @@
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ru.practicum.shareit.exception.ValidationException;
-import ru.practicum.shareit.item.model.Item;
+import ru.practicum.shareit.item.dto.ItemNewDto;
/**
* Утилитарный класс для валидации сущности Item
@@ -15,34 +15,33 @@
public class ItemValidate {
// Валидация при создании вещи
- public static void validateForCreate(Item item) {
- validateNotNull(item);
- validateName(item.getName());
- validateDescription(item.getDescription());
- validateAvailability(item.getAvailable());
- validateOwner(item.getOwnerId());
+ public static void validateForCreate(ItemNewDto itemNewDto) {
+ validateNotNull(itemNewDto);
+ validateName(itemNewDto.getName());
+ validateDescription(itemNewDto.getDescription());
+ validateAvailability(itemNewDto.getAvailable());
}
// Валидация при изменении вещи
- public static void validateForUpdate(Item item) {
- validateNotNull(item);
+ public static void validateForUpdate(ItemNewDto itemNewDto) {
+ validateNotNull(itemNewDto);
- if (item.getName() != null) {
- validateName(item.getName());
+ if (itemNewDto.getName() != null) {
+ validateName(itemNewDto.getName());
}
- if (item.getDescription() != null) {
- validateDescription(item.getDescription());
+ if (itemNewDto.getDescription() != null) {
+ validateDescription(itemNewDto.getDescription());
}
- if (item.getAvailable() != null) {
- validateAvailability(item.getAvailable());
+ if (itemNewDto.getAvailable() != null) {
+ validateAvailability(itemNewDto.getAvailable());
}
}
// Вспомогательные методы для валидации полей Item
- private static void validateNotNull(Item item) {
- if (item == null) {
+ private static void validateNotNull(ItemNewDto itemNewDto) {
+ if (itemNewDto == null) {
log.error("Попытка добавить или изменить null, а не вещь");
throw new ValidationException("Вещь не может быть null");
}
@@ -68,11 +67,4 @@ private static void validateAvailability(Boolean available) {
throw new ValidationException("Статус доступности вещи должен быть указан");
}
}
-
- private static void validateOwner(Long ownerId) {
- if (ownerId == null || ownerId <= 0) {
- log.error("Попытка добавить вещь с некорректным Id владельца");
- throw new ValidationException("Некорректный Id владельца");
- }
- }
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java
index 0abeaa7..5e8b8cd 100644
--- a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java
+++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java
@@ -1,7 +1,28 @@
package ru.practicum.shareit.request.model;
+import jakarta.persistence.*;
+import lombok.*;
+
/**
* TODO Sprint add-item-requests.
*/
+@Getter
+@Setter
+@ToString
+@Entity
+@Table(name = "items")
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder(toBuilder = true)
public class ItemRequest {
-}
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "description")
+ private String description;
+
+ @Column(name = "requestor_id")
+ private long requestorId;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/controller/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java
similarity index 62%
rename from src/main/java/ru/practicum/shareit/user/controller/UserController.java
rename to src/main/java/ru/practicum/shareit/user/UserController.java
index ba53c47..c1e579c 100644
--- a/src/main/java/ru/practicum/shareit/user/controller/UserController.java
+++ b/src/main/java/ru/practicum/shareit/user/UserController.java
@@ -1,21 +1,17 @@
-package ru.practicum.shareit.user.controller;
+package ru.practicum.shareit.user;
-import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import ru.practicum.shareit.user.dto.UserDto;
-import ru.practicum.shareit.user.service.UserService;
+import ru.practicum.shareit.user.dto.UserNewDto;
import java.util.Collection;
-/**
- * TODO Sprint add-controllers.
- */
@Slf4j
@RestController
@RequiredArgsConstructor
-@RequestMapping(path = "/users")
+@RequestMapping("/users")
public class UserController {
private final UserService userService;
@@ -26,27 +22,27 @@ public Collection findAll() {
return userService.findAll();
}
- @GetMapping("/{id}")
- public UserDto findById(@NonNull @PathVariable("id") Long userId) {
+ @GetMapping("/{userId}")
+ public UserDto findById(@PathVariable Long userId) {
log.info("Запрос на получение пользователя с Id:{}", userId);
return userService.findById(userId);
}
@PostMapping
- public UserDto create(@NonNull @RequestBody UserDto newUser) {
+ public UserDto create(@RequestBody UserNewDto newUser) {
log.info("Запрос на добавление нового пользователя");
return userService.create(newUser);
}
- @PatchMapping("/{id}")
- public UserDto update(@NonNull @PathVariable("id") Long userId,
- @NonNull @RequestBody UserDto updatedUser) {
- log.info("Запрос на обновление пользователя с id:{}", updatedUser.getId());
+ @PatchMapping("/{userId}")
+ public UserDto update(@PathVariable Long userId,
+ @RequestBody UserNewDto updatedUser) {
+ log.info("Запрос на обновление пользователя с id:{}", userId);
return userService.update(userId, updatedUser);
}
- @DeleteMapping("/{id}")
- public void delete(@NonNull @PathVariable("id") Long userId) {
+ @DeleteMapping("/{userId}")
+ public void delete(@PathVariable Long userId) {
log.info("Запрос на удаление пользователя с id:{}", userId);
userService.delete(userId);
}
diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/src/main/java/ru/practicum/shareit/user/UserService.java
similarity index 54%
rename from src/main/java/ru/practicum/shareit/user/service/UserService.java
rename to src/main/java/ru/practicum/shareit/user/UserService.java
index 6fa8bd2..c25f390 100644
--- a/src/main/java/ru/practicum/shareit/user/service/UserService.java
+++ b/src/main/java/ru/practicum/shareit/user/UserService.java
@@ -1,6 +1,7 @@
-package ru.practicum.shareit.user.service;
+package ru.practicum.shareit.user;
import ru.practicum.shareit.user.dto.UserDto;
+import ru.practicum.shareit.user.dto.UserNewDto;
import java.util.Collection;
@@ -9,9 +10,9 @@ public interface UserService {
UserDto findById(Long userId);
- UserDto create(UserDto newUser);
+ UserDto create(UserNewDto newUser);
- UserDto update(Long userId, UserDto updatedUser);
+ UserDto update(Long userId, UserNewDto updatedUser);
void delete(Long userId);
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java
similarity index 66%
rename from src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java
rename to src/main/java/ru/practicum/shareit/user/UserServiceImpl.java
index 3476523..74c0012 100644
--- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java
+++ b/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java
@@ -1,4 +1,4 @@
-package ru.practicum.shareit.user.service;
+package ru.practicum.shareit.user;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -6,11 +6,13 @@
import ru.practicum.shareit.exception.*;
import ru.practicum.shareit.user.dao.UserRepository;
import ru.practicum.shareit.user.dto.UserDto;
+import ru.practicum.shareit.user.dto.UserNewDto;
import ru.practicum.shareit.user.mapper.UserMapper;
import ru.practicum.shareit.user.model.User;
import ru.practicum.shareit.user.utils.UserValidate;
import java.util.Collection;
+import java.util.stream.Collectors;
@Slf4j
@Service
@@ -24,7 +26,7 @@ public class UserServiceImpl implements UserService {
public Collection findAll() {
return userRepository.findAll().stream()
.map(UserMapper::mapToDto)
- .toList();
+ .collect(Collectors.toList());
}
// Получение пользователя по id
@@ -37,48 +39,47 @@ public UserDto findById(Long userId) {
// Добавление пользователя
@Override
- public UserDto create(UserDto userDto) {
- User user = UserMapper.mapToUser(userDto);
- UserValidate.validateForCreate(user);
- checkUniqueEmail(userDto.getEmail());
-
- User createdUser = userRepository.create(user);
+ public UserDto create(UserNewDto userNewDto) {
+ UserValidate.validateForCreate(userNewDto);
+ checkUniqueEmail(userNewDto.getEmail());
+ User user = UserMapper.mapToUser(userNewDto);
+ User createdUser = userRepository.save(user);
return UserMapper.mapToDto(createdUser);
}
// Обновление пользователя
@Override
- public UserDto update(Long userId, UserDto userDto) {
+ public UserDto update(Long userId, UserNewDto userNewDto) {
if (userId == null || userId <= 0) {
throw new ValidationException("Некорректный ID пользователя");
}
- if (userDto == null) {
- throw new ValidationException("Пользователь не может быть null");
- }
+ UserValidate.validateForUpdate(userNewDto);
User existingUser = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден"));
- if (userDto.getName() != null) {
- existingUser.setName(userDto.getName());
+ if (userNewDto.getName() != null) {
+ existingUser.setName(userNewDto.getName());
}
- if (userDto.getEmail() != null && !userDto.getEmail().equalsIgnoreCase(existingUser.getEmail())) {
- checkUniqueEmail(userDto.getEmail());
- existingUser.setEmail(userDto.getEmail());
+ if (userNewDto.getEmail() != null && !userNewDto.getEmail().equalsIgnoreCase(existingUser.getEmail())) {
+ checkUniqueEmail(userNewDto.getEmail());
+ existingUser.setEmail(userNewDto.getEmail());
}
- UserValidate.validateForEdit(existingUser);
-
- User updatedUser = userRepository.update(userId, existingUser);
+ User updatedUser = userRepository.save(existingUser);
return UserMapper.mapToDto(updatedUser);
}
// Удаление пользователя
@Override
public void delete(Long userId) {
- userRepository.delete(userId);
+ if (!userRepository.existsById(userId)) {
+ throw new NotFoundException("Пользователь с Id:" + userId + "не найден");
+ }
+
+ userRepository.deleteById(userId);
}
// Проверки уникальности email
diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java b/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java
index 4e4d535..896e052 100644
--- a/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java
+++ b/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java
@@ -1,18 +1,10 @@
package ru.practicum.shareit.user.dao;
+import org.springframework.data.jpa.repository.JpaRepository;
import ru.practicum.shareit.user.model.User;
-import java.util.Collection;
import java.util.Optional;
-public interface UserRepository {
- Collection findAll();
-
- Optional findById(Long userId);
-
- User create(User user);
-
- User update(Long userId, User user);
-
- void delete(Long userId);
+public interface UserRepository extends JpaRepository {
+ Optional findByEmail(String email);
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserRepositoryImpl.java b/src/main/java/ru/practicum/shareit/user/dao/UserRepositoryImpl.java
deleted file mode 100644
index 6343bbc..0000000
--- a/src/main/java/ru/practicum/shareit/user/dao/UserRepositoryImpl.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package ru.practicum.shareit.user.dao;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Repository;
-import ru.practicum.shareit.user.model.User;
-
-import java.util.*;
-
-@Slf4j
-@Repository
-public class UserRepositoryImpl implements UserRepository {
-
- private final Map users = new HashMap<>();
- private final Set usersEmail = new HashSet<>();
- private long idCounter = 0L;
-
- @Override
- public Collection findAll() {
- return List.copyOf(users.values());
- }
-
- @Override
- public Optional findById(Long userId) {
- return Optional.ofNullable(users.get(userId));
- }
-
- @Override
- public User create(User user) {
- user.setUserId(++idCounter);
- usersEmail.add(user.getEmail().toLowerCase());
- users.put(user.getUserId(), user);
- return user;
- }
-
- @Override
- public User update(Long userId, User user) {
- users.put(user.getUserId(), user);
- return user;
- }
-
- @Override
- public void delete(Long userId) {
- usersEmail.remove(users.get(userId).getEmail());
- users.remove(userId);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java
index cfbdbf3..93c9e28 100644
--- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java
+++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java
@@ -1,9 +1,11 @@
package ru.practicum.shareit.user.dto;
import lombok.Builder;
-import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
-@Data
+@Getter
+@Setter
@Builder(toBuilder = true)
public class UserDto {
private Long id;
diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserNewDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserNewDto.java
new file mode 100644
index 0000000..1825f4c
--- /dev/null
+++ b/src/main/java/ru/practicum/shareit/user/dto/UserNewDto.java
@@ -0,0 +1,14 @@
+package ru.practicum.shareit.user.dto;
+
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder(toBuilder = true)
+public class UserNewDto {
+
+ private String name;
+ private String email;
+}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java
index 130cba6..3509daa 100644
--- a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java
+++ b/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java
@@ -1,6 +1,7 @@
package ru.practicum.shareit.user.mapper;
import ru.practicum.shareit.user.dto.UserDto;
+import ru.practicum.shareit.user.dto.UserNewDto;
import ru.practicum.shareit.user.model.User;
public class UserMapper {
@@ -8,18 +9,17 @@ public class UserMapper {
// Преобразование User в Dto
public static UserDto mapToDto(User user) {
return UserDto.builder()
- .id(user.getUserId())
+ .id(user.getId())
.name(user.getName())
.email(user.getEmail())
.build();
}
// Преобразование Dto в User
- public static User mapToUser(UserDto userDto) {
+ public static User mapToUser(UserNewDto userNewDto) {
return User.builder()
- .userId(userDto.getId())
- .name(userDto.getName())
- .email(userDto.getEmail())
+ .name(userNewDto.getName())
+ .email(userNewDto.getEmail())
.build();
}
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java
index a1b5375..ffabf45 100644
--- a/src/main/java/ru/practicum/shareit/user/model/User.java
+++ b/src/main/java/ru/practicum/shareit/user/model/User.java
@@ -1,15 +1,43 @@
package ru.practicum.shareit.user.model;
-import lombok.Builder;
-import lombok.Data;
+import jakarta.persistence.*;
+import lombok.*;
-/**
- * TODO Sprint add-controllers.
- */
-@Data
+@Getter
+@Setter
+@ToString
+@Entity
+@Table(name = "users", schema = "public")
+@NoArgsConstructor
+@AllArgsConstructor
@Builder(toBuilder = true)
public class User {
- private Long userId;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "name", nullable = false)
private String name;
+
+ @Column(name = "email", nullable = false, unique = true)
private String email;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof User)) {
+ return false;
+ }
+
+ return id != null && id.equals(((User) o).getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java b/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java
index fc2e208..ac415d6 100644
--- a/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java
+++ b/src/main/java/ru/practicum/shareit/user/utils/UserValidate.java
@@ -4,7 +4,7 @@
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ru.practicum.shareit.exception.ValidationException;
-import ru.practicum.shareit.user.model.User;
+import ru.practicum.shareit.user.dto.UserNewDto;
/**
* Утилитарный класс для валидации данных сущности User
@@ -15,17 +15,29 @@
public class UserValidate {
// Валидация при создании пользователя
- public static void validateForCreate(User user) {
- validateNotNull(user);
- validateEmail(user.getEmail());
+ public static void validateForCreate(UserNewDto userNewDto) {
+ validateNotNull(userNewDto);
+ validateName(userNewDto.getName());
+ validateEmail(userNewDto.getEmail());
}
// Валидация при изменении пользователя
- public static void validateForEdit(User user) {
- validateNotNull(user);
+ public static void validateForUpdate(UserNewDto userNewDto) {
+ validateNotNull(userNewDto);
- if (user.getEmail() != null) {
- validateEmail(user.getEmail());
+ if (userNewDto.getName() != null) {
+ validateName(userNewDto.getName());
+ }
+
+ if (userNewDto.getEmail() != null) {
+ validateEmail(userNewDto.getEmail());
+ }
+ }
+
+ // Вспомогательный метод валидации имени
+ private static void validateName(String name) {
+ if (name == null || name.isBlank()) {
+ throw new ValidationException("Имя пользователя не может быть пустым");
}
}
@@ -42,8 +54,8 @@ private static void validateEmail(String email) {
}
// Вспомогательный метод проверки пользователя на null
- private static void validateNotNull(User user) {
- if (user == null) {
+ private static void validateNotNull(UserNewDto userNewDto) {
+ if (userNewDto == null) {
log.error("Попытка добавить или изменить null, а не пользователя");
throw new ValidationException("Пользователь не может быть null");
}
diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties
index 9e9bc4b..71caf05 100644
--- a/src/main/resources/application-test.properties
+++ b/src/main/resources/application-test.properties
@@ -7,7 +7,7 @@ logging.level.org.springframework.transaction.interceptor=TRACE
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
# TODO Append connection to H2 DB
-#spring.datasource.driverClassName
-#spring.datasource.url
-#spring.datasource.username
-#spring.datasource.password
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:shareit;MODE=PostgreSQL
+spring.datasource.username=sa
+spring.datasource.password=password
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 51c5180..713d82b 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,14 +1,13 @@
+server.port=8080
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 Postgres DB
-#spring.datasource.driverClassName
-#spring.datasource.url
-#spring.datasource.username
-#spring.datasource.password
+spring.datasource.driverClassName=org.postgresql.Driver
+spring.datasource.url=jdbc:postgresql://localhost:5432/shareit
+spring.datasource.username=dbuser
+spring.datasource.password=12345
\ 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..62c9bd7
--- /dev/null
+++ b/src/main/resources/schema.sql
@@ -0,0 +1,45 @@
+CREATE TABLE IF NOT EXISTS users (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ email VARCHAR(100) NOT NULL UNIQUE
+);
+
+CREATE TABLE IF NOT EXISTS requests (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ description VARCHAR(250),
+ requester_id BIGINT NOT NULL,
+ FOREIGN KEY (requester_id) REFERENCES users (id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS items (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ description VARCHAR(500),
+ is_available BOOLEAN NOT NULL,
+ owner_id BIGINT NOT NULL,
+ request_id BIGINT,
+ FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE CASCADE,
+ FOREIGN KEY (request_id) REFERENCES requests (id) ON DELETE SET NULL
+);
+
+CREATE TABLE IF NOT EXISTS bookings (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ start_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ end_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ item_id BIGINT NOT NULL,
+ booker_id BIGINT NOT NULL,
+ status VARCHAR(30) NOT NULL,
+
+ FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE,
+ FOREIGN KEY (booker_id) REFERENCES users (id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS comments (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ text VARCHAR(1000) NOT NULL,
+ item_id BIGINT NOT NULL,
+ author_id BIGINT NOT NULL,
+ created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE,
+ FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE CASCADE
+);
\ No newline at end of file
diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/src/test/java/ru/practicum/shareit/ShareItTests.java
index 4d79052..8cb88ed 100644
--- a/src/test/java/ru/practicum/shareit/ShareItTests.java
+++ b/src/test/java/ru/practicum/shareit/ShareItTests.java
@@ -9,5 +9,4 @@ class ShareItTests {
@Test
void contextLoads() {
}
-
-}
+}
\ No newline at end of file