Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
495 changes: 259 additions & 236 deletions pom.xml

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/BookingController.java
Original file line number Diff line number Diff line change
@@ -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<BookingDto> 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<BookingDto> findByBookerId(@RequestHeader("X-Sharer-User-Id") Long bookerId,
@RequestParam(name = "state", defaultValue = "ALL")
BookingState bookingState) {
log.info("Запрос на получение списка бронирований пользователя с id:{}", bookerId);
return bookingService.findByBookerId(bookerId, bookingState);
}
}
20 changes: 20 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/BookingService.java
Original file line number Diff line number Diff line change
@@ -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<BookingDto> findByOwnerId(Long ownerId, BookingState bookingState);

Collection<BookingDto> findByBookerId(Long bookerId, BookingState bookingState);
}
218 changes: 218 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java
Original file line number Diff line number Diff line change
@@ -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<BookingDto> 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<Booking> 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<BookingDto> 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<Booking> 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());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<Booking, Long> {

Collection<Booking> 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<Booking> findByBookerIdStateCurrent(Long bookerId);

@Query("SELECT b FROM Booking b " +
"WHERE b.booker.id = :bookerId " +
"AND b.end < CURRENT_TIMESTAMP " +
"AND b.status = 'APPROVED'")
Collection<Booking> findByBookerIdStatePast(Long bookerId);

@Query("SELECT b FROM Booking b " +
"WHERE b.booker.id = :bookerId " +
"AND b.start > CURRENT_TIMESTAMP " +
"AND b.status = 'APPROVED'")
Collection<Booking> findByBookerIdStateFuture(Long bookerId);

Collection<Booking> findByBookerIdAndStatus(Long bookerId, BookingStatus status);

Collection<Booking> 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<Booking> 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<Booking> 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<Booking> findByOwnerIdStateFuture(Long ownerId);

Collection<Booking> 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<Booking> 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<Booking> 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<Booking> findByBookerIdLastBookingsForItem(Long bookerId, Long itemId);
}
Loading