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
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/ru/practicum/shareit/booking/Booking.java

This file was deleted.

42 changes: 40 additions & 2 deletions src/main/java/ru/practicum/shareit/booking/BookingController.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
package ru.practicum.shareit.booking;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import ru.practicum.shareit.booking.dto.BookingDto;
import ru.practicum.shareit.booking.dto.NewBookingRequest;

import java.util.Collection;

/**
* TODO Sprint add-bookings.
*/
@RestController
@RequestMapping(path = "/bookings")
@RequiredArgsConstructor
public class BookingController {
private final BookingService bookingService;

@GetMapping("/{id}")
public BookingDto getBooking(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long id) {
return bookingService.getBookingById(id, userId);
}

@GetMapping
public Collection<BookingDto> getBookingsByBooker(@RequestHeader("X-Sharer-User-Id") Long userId,
@RequestParam(defaultValue = "ALL", required = false) String state) {
return bookingService.getAllBookingsByBooker(userId, state);
}

@GetMapping("/owner")
public Collection<BookingDto> getBookingsByItemOwner(@RequestHeader("X-Sharer-User-Id") Long userId,
@RequestParam(defaultValue = "ALL", required = false) String state) {
return bookingService.getAllBookingsByOwner(userId, state);
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public BookingDto createBooking(@RequestHeader("X-Sharer-User-Id") Long userId,
@Valid @RequestBody NewBookingRequest request) {
return bookingService.createBooking(request, userId);
}

@PatchMapping("/{bookingId}")
public BookingDto approveBooking(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long bookingId,
@RequestParam Boolean approved) {
return bookingService.approveBooking(bookingId, userId, approved);
}
}
79 changes: 79 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/BookingRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package ru.practicum.shareit.booking;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ru.practicum.shareit.booking.model.Booking;
import ru.practicum.shareit.booking.model.BookingStatus;

import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

public interface BookingRepository extends JpaRepository<Booking, Long> {

@Query("SELECT b FROM Booking b " +
"WHERE b.id = :id AND (b.booker.id = :userId OR b.item.owner.id = :userId)")
Optional<BookingShort> findByItemOwnerOrBooker(@Param("id") Long id, @Param("userId") Long userId);

Optional<BookingShort> findBookingById(Long id);

@Query("SELECT b FROM Booking b " +
"WHERE b.item.id = :itemId " +
"AND b.end < :now " +
"AND b.status = ru.practicum.shareit.booking.model.BookingStatus.APPROVED")
List<BookingShortForItem> findLastBookingForItem(@Param("itemId") Long itemId,
@Param("now") OffsetDateTime now, Pageable pageable);

@Query("SELECT b FROM Booking b " +
"WHERE b.item.id = :itemId " +
"AND b.start > :now " +
"AND b.status = ru.practicum.shareit.booking.model.BookingStatus.APPROVED")
List<BookingShortForItem> findNextBookingForItem(@Param("itemId") Long itemId,
@Param("now") OffsetDateTime now, Pageable pageable);

@Modifying
@Query("UPDATE Booking b SET b.status = :status WHERE b.id = :id AND b.item.owner.id = :ownerId")
int updateStatusByIdAndOwnerId(@Param("id") Long id, @Param("ownerId") Long ownerId,
@Param("status") BookingStatus status);

Collection<BookingShort> findByBookerId(Long bookerId, Sort sort);

Collection<BookingShort> findByBookerIdAndStatusAndStartBeforeAndEndAfter(
Long bookerId,
BookingStatus status,
OffsetDateTime start,
OffsetDateTime end,
Sort sort
);

Collection<BookingShort> findByBookerIdAndStatusAndEndBefore(Long bookerId, BookingStatus status,
OffsetDateTime end, Sort sort);

Collection<BookingShort> findByBookerIdAndStatus(Long bookerId, BookingStatus status, Sort sort);

Collection<BookingShort> findByBookerIdAndStatusAndStartAfter(Long bookerId, BookingStatus status,
OffsetDateTime start, Sort sort);

Collection<BookingShort> findByItemOwnerId(Long ownerId, Sort sort);

Collection<BookingShort> findByItemOwnerIdAndStatus(Long ownerId, BookingStatus status, Sort sort);

Collection<BookingShort> findByItemOwnerIdAndStatusAndStartBeforeAndEndAfter(
Long ownerId,
BookingStatus status,
OffsetDateTime start,
OffsetDateTime end,
Sort sort
);

Collection<BookingShort> findByItemOwnerIdAndStatusAndEndBefore(Long ownerId, BookingStatus status,
OffsetDateTime end, Sort sort);

Collection<BookingShort> findByItemOwnerIdAndStatusAndStartAfter(Long ownerId, BookingStatus status,
OffsetDateTime start, Sort sort);
}
109 changes: 109 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,109 @@
package ru.practicum.shareit.booking;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.practicum.shareit.booking.dto.BookingDto;
import ru.practicum.shareit.booking.dto.NewBookingRequest;
import ru.practicum.shareit.booking.mapper.BookingMapper;
import ru.practicum.shareit.booking.model.Booking;
import ru.practicum.shareit.booking.model.BookingStatus;
import ru.practicum.shareit.exception.NotFoundException;
import ru.practicum.shareit.exception.ServerException;
import ru.practicum.shareit.item.ItemValidationService;
import ru.practicum.shareit.item.model.Item;
import ru.practicum.shareit.user.model.User;
import ru.practicum.shareit.user.UserValidationService;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Collection;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class BookingService {
private final BookingRepository bookingRepository;
private final ItemValidationService itemValidationService;
private final UserValidationService userValidationService;

public BookingDto getBookingById(Long id, Long userId) {
userValidationService.isUserExistOrThrowNotFound(userId);
return bookingRepository.findByItemOwnerOrBooker(id, userId)
.map(BookingMapper::toBookingDto)
.orElseThrow(() -> new NotFoundException("Booking not found"));
}

public Collection<BookingDto> getAllBookingsByBooker(Long userId, String state) {
userValidationService.isUserExistOrThrowNotFound(userId);
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
Sort sort = Sort.by(Sort.Direction.DESC, "start");
Collection<BookingShort> bookings = switch (state) {
case "ALL" -> bookingRepository.findByBookerId(userId, sort);
case "WAITING" -> bookingRepository.findByBookerIdAndStatus(userId, BookingStatus.WAITING, sort);
case "REJECTED" -> bookingRepository.findByBookerIdAndStatus(userId, BookingStatus.REJECTED, sort);
case "CURRENT" -> bookingRepository.findByBookerIdAndStatusAndStartBeforeAndEndAfter(userId,
BookingStatus.APPROVED, now, now, sort);
case "PAST" ->
bookingRepository.findByBookerIdAndStatusAndEndBefore(userId, BookingStatus.APPROVED, now, sort);
case "FUTURE" ->
bookingRepository.findByBookerIdAndStatusAndStartAfter(userId, BookingStatus.APPROVED, now, sort);
default -> throw new ServerException("Invalid state");
};

return bookings.stream().map(BookingMapper::toBookingDto).toList();
}

public Collection<BookingDto> getAllBookingsByOwner(Long userId, String state) {
userValidationService.isUserExistOrThrowNotFound(userId);
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
Sort sort = Sort.by(Sort.Direction.DESC, "start");
Collection<BookingShort> bookings = switch (state) {
case "ALL" -> bookingRepository.findByItemOwnerId(userId, sort);
case "WAITING" -> bookingRepository.findByItemOwnerIdAndStatus(userId, BookingStatus.WAITING, sort);
case "REJECTED" -> bookingRepository.findByItemOwnerIdAndStatus(userId, BookingStatus.REJECTED, sort);
case "CURRENT" -> bookingRepository.findByItemOwnerIdAndStatusAndStartBeforeAndEndAfter(userId,
BookingStatus.APPROVED, now, now, sort);
case "PAST" ->
bookingRepository.findByItemOwnerIdAndStatusAndEndBefore(userId, BookingStatus.APPROVED, now, sort);
case "FUTURE" ->
bookingRepository.findByItemOwnerIdAndStatusAndStartAfter(userId, BookingStatus.APPROVED, now, sort);
default -> throw new ServerException("Invalid state");
};

return bookings.stream().map(BookingMapper::toBookingDto).toList();
}

@Transactional
public BookingDto createBooking(NewBookingRequest request, Long userId) {
User booker = userValidationService.isUserExistOrThrowNotFound(userId);
Item item = itemValidationService.isItemExistOrThrowNotFound(request.getItemId());

if (item.getAvailable().equals(Boolean.FALSE)) {
throw new ServerException("Item is not available");
}

Booking booking = BookingMapper.toBooking(request, booker, item);

booking.setStatus(BookingStatus.WAITING);

bookingRepository.save(booking);

return BookingMapper.toBookingDto(booking);
}

@Transactional
public BookingDto approveBooking(Long id, Long userId, Boolean approved) {
BookingStatus status = approved ? BookingStatus.APPROVED : BookingStatus.REJECTED;
int updatedRows = bookingRepository.updateStatusByIdAndOwnerId(id, userId, status);

if (updatedRows == 0) {
throw new ServerException("Booking not updated");
}

return bookingRepository.findBookingById(id)
.map(BookingMapper::toBookingDto)
.orElseThrow(() -> new NotFoundException("Booking not found"));
}
}
29 changes: 29 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/BookingShort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ru.practicum.shareit.booking;

import ru.practicum.shareit.booking.model.BookingStatus;

import java.time.OffsetDateTime;

public interface BookingShort {
Long getId();

OffsetDateTime getStart();

OffsetDateTime getEnd();

BookingStatus getStatus();

UserInfo getBooker();

ItemInfo getItem();

interface UserInfo {
Long getId();
}

interface ItemInfo {
Long getId();

String getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.practicum.shareit.booking;

import java.time.OffsetDateTime;

public interface BookingShortForItem {
Long getId();

UserInfo getBooker();

OffsetDateTime getStart();

OffsetDateTime getEnd();

interface UserInfo {
Long getId();
}

}
33 changes: 33 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
package ru.practicum.shareit.booking.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import ru.practicum.shareit.booking.model.BookingStatus;
import ru.practicum.shareit.serializer.OffsetDateTimeToLocalDateTimeSerializer;

import java.time.OffsetDateTime;

/**
* TODO Sprint add-bookings.
*/
@Data
public class BookingDto {
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id;

@JsonSerialize(using = OffsetDateTimeToLocalDateTimeSerializer.class)
private OffsetDateTime start;

@JsonSerialize(using = OffsetDateTimeToLocalDateTimeSerializer.class)
private OffsetDateTime end;
private BookingStatus status;
private Booker booker;
private Item item;

@Data
public static class Booker {
private Long id;
}

@Data
public static class Item {
private Long id;
private String name;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.practicum.shareit.booking.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import ru.practicum.shareit.serializer.OffsetDateTimeToLocalDateTimeSerializer;

import java.time.OffsetDateTime;

@Data
public class BookingForItemDto {
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id;
private Long bookerId;

@JsonSerialize(using = OffsetDateTimeToLocalDateTimeSerializer.class)
private OffsetDateTime start;

@JsonSerialize(using = OffsetDateTimeToLocalDateTimeSerializer.class)
private OffsetDateTime end;
}
Loading