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
14 changes: 14 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
Expand Down Expand Up @@ -71,6 +81,10 @@
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/BookingMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ru.practicum.shareit.booking;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import ru.practicum.shareit.booking.dto.BookingDto;
import ru.practicum.shareit.booking.dto.BookingShortDto;
import ru.practicum.shareit.booking.model.Booking;
import ru.practicum.shareit.item.ItemMapper;
import ru.practicum.shareit.user.UserMapper;

@Mapper(componentModel = "spring",
uses = {ItemMapper.class, UserMapper.class},
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface BookingMapper {

@Mapping(target = "item", source = "item")
@Mapping(target = "booker", source = "booker")
BookingDto toBookingDto(Booking booking);

@Mapping(target = "id", ignore = true)
Booking toBooking(BookingDto bookingDto);

Booking updateBookingFields(@MappingTarget Booking targetBooking, BookingDto sourceBookingDto);

@Mapping(target = "id", source = "id")
@Mapping(target = "bookerId", source = "booker.id")
BookingShortDto toBookingShortDto(Booking booking);
}
10 changes: 10 additions & 0 deletions src/main/java/ru/practicum/shareit/booking/BookingState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ru.practicum.shareit.booking;

public enum BookingState {
ALL,
CURRENT,
PAST,
FUTURE,
WAITING,
REJECTED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package ru.practicum.shareit.booking.Impl;

import org.springframework.stereotype.Service;
import ru.practicum.shareit.booking.BookingMapper;
import ru.practicum.shareit.booking.BookingState;
import ru.practicum.shareit.booking.BookingStatus;
import ru.practicum.shareit.booking.dto.BookingDto;
import ru.practicum.shareit.booking.exception.ShareItException;
import ru.practicum.shareit.booking.model.Booking;
import ru.practicum.shareit.booking.repository.BookingRepository;
import ru.practicum.shareit.booking.service.BookerStateProcessor;
import ru.practicum.shareit.booking.service.BookingService;
import ru.practicum.shareit.booking.service.handler.owner.OwnerStateProcessor;
import ru.practicum.shareit.item.model.Item;
import ru.practicum.shareit.item.repository.ItemRepository;
import ru.practicum.shareit.user.model.User;
import ru.practicum.shareit.user.repository.UserRepository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class BookingServiceImpl implements BookingService {
private final BookingRepository bookingRepository;
private final UserRepository userRepository;
private final ItemRepository itemRepository;
private final BookingMapper bookingMapper;
private final BookerStateProcessor bookerStateProcessor;
private final OwnerStateProcessor ownerStateProcessor;

public BookingServiceImpl(BookingRepository bookingRepository,
UserRepository userRepository,
ItemRepository itemRepository,
BookingMapper bookingMapper,
BookerStateProcessor bookerStateProcessor,
OwnerStateProcessor ownerStateProcessor) {
this.bookingRepository = bookingRepository;
this.userRepository = userRepository;
this.itemRepository = itemRepository;
this.bookingMapper = bookingMapper;
this.bookerStateProcessor = bookerStateProcessor;
this.ownerStateProcessor = ownerStateProcessor;
}

@Override
public BookingDto create(BookingDto bookingDto, Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден"));

if (bookingDto.getItemId() == null) {
throw new ShareItException.BadRequestException("ID предмета не указан");
}

Item item = itemRepository.findById(bookingDto.getItemId())
.orElseThrow(() -> new ShareItException.NotFoundException("Предмет с id " + bookingDto.getItemId() + " не найден"));

if (!item.getAvailable()) {
throw new ShareItException.BadRequestException("Предмет недоступен для бронирования");
}

if (item.getOwner().getId().equals(userId)) {
throw new ShareItException.NotFoundException("Владелец не может бронировать свой предмет");
}

LocalDateTime now = LocalDateTime.now();
if (bookingDto.getStart() != null && bookingDto.getEnd() != null) {
if (bookingDto.getEnd().isBefore(bookingDto.getStart()) || bookingDto.getEnd().equals(bookingDto.getStart())) {
throw new ShareItException.BadRequestException("Дата окончания должна быть позже даты начала");
}
}

Booking booking = new Booking();
booking.setStart(bookingDto.getStart());
booking.setEnd(bookingDto.getEnd());
booking.setItem(item);
booking.setBooker(user);
booking.setStatus(BookingStatus.WAITING);

Booking savedBooking = bookingRepository.save(booking);
return bookingMapper.toBookingDto(savedBooking);
}

@Override
public BookingDto approve(Long bookingId, Long userId, Boolean approved) {
Booking booking = bookingRepository.findById(bookingId)
.orElseThrow(() -> new ShareItException.NotFoundException("Бронирование с id " + bookingId + " не найдено"));

if (!booking.getItem().getOwner().getId().equals(userId)) {
throw new ShareItException.ForbiddenException("Только владелец предмета может подтверждать бронирование");
}

if (booking.getStatus() != BookingStatus.WAITING) {
throw new ShareItException.BadRequestException("Бронирование уже обработано");
}

booking.setStatus(approved ? BookingStatus.APPROVED : BookingStatus.REJECTED);
return bookingMapper.toBookingDto(bookingRepository.save(booking));
}

@Override
public BookingDto getById(Long bookingId, Long userId) {
Booking booking = bookingRepository.findById(bookingId)
.orElseThrow(() -> new ShareItException.NotFoundException("Бронирование с id " + bookingId + " не найдено"));

if (!booking.getBooker().getId().equals(userId) && !booking.getItem().getOwner().getId().equals(userId)) {
throw new ShareItException.NotFoundException("Доступ запрещен");
}

return bookingMapper.toBookingDto(booking);
}

@Override
public List<BookingDto> getAllByBooker(Long userId, String stateParam) {
userRepository.findById(userId)
.orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден"));

BookingState state;
try {
state = BookingState.valueOf(stateParam.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ShareItException.BadRequestException("Неизвестный статус: " + stateParam);
}

LocalDateTime now = LocalDateTime.now();
List<Booking> bookings = bookerStateProcessor.process(state, userId, now);

return bookings.stream()
.map(bookingMapper::toBookingDto)
.collect(Collectors.toList());
}

@Override
public List<BookingDto> getAllByOwner(Long userId, String stateParam) {
userRepository.findById(userId)
.orElseThrow(() -> new ShareItException.NotFoundException("Пользователь с id " + userId + " не найден"));

BookingState state;
try {
state = BookingState.valueOf(stateParam.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ShareItException.BadRequestException("Неизвестный статус: " + stateParam);
}

LocalDateTime now = LocalDateTime.now();
List<Booking> bookings = ownerStateProcessor.process(state, userId, now);

return bookings.stream()
.map(bookingMapper::toBookingDto)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
package ru.practicum.shareit.booking.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ru.practicum.shareit.booking.dto.BookingDto;
import ru.practicum.shareit.booking.service.BookingService;

import java.util.List;

@RestController
@RequestMapping(path = "/bookings")
@RequiredArgsConstructor
public class BookingController {
private final BookingService bookingService;

@PostMapping
public ResponseEntity<BookingDto> createBooking(@Valid @RequestBody BookingDto bookingDto,
@RequestHeader("X-Sharer-User-Id") Long userId) {
return ResponseEntity.ok(bookingService.create(bookingDto, userId));
}

@PatchMapping("/{bookingId}")
public ResponseEntity<BookingDto> approve(@PathVariable Long bookingId,
@RequestHeader("X-Sharer-User-Id") Long userId,
@RequestParam Boolean approved) {
return ResponseEntity.ok(bookingService.approve(bookingId, userId, approved));
}

@GetMapping("/{bookingId}")
public ResponseEntity<BookingDto> getById(@PathVariable Long bookingId,
@RequestHeader("X-Sharer-User-Id") Long userId) {
return ResponseEntity.ok(bookingService.getById(bookingId, userId));
}

@GetMapping
public ResponseEntity<List<BookingDto>> getAllByBooker(
@RequestHeader("X-Sharer-User-Id") Long userId,
@RequestParam(defaultValue = "ALL") String state) {
return ResponseEntity.ok(bookingService.getAllByBooker(userId, state));
}

@GetMapping("/owner")
public ResponseEntity<List<BookingDto>> getAllByOwner(
@RequestHeader("X-Sharer-User-Id") Long userId,
@RequestParam(defaultValue = "ALL") String state) {
return ResponseEntity.ok(bookingService.getAllByOwner(userId, state));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ru.practicum.shareit.booking.dto;

import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
Expand All @@ -9,20 +11,25 @@

import java.time.LocalDateTime;


@Getter
@Setter
@Builder(toBuilder = true)
public class BookingDto {
private Long id;

@NotNull(message = "Дата начала бронирования не может быть пустой")
@Future(message = "Дата начала бронирования должна быть в будущем")
private LocalDateTime start;

@NotNull(message = "Дата окончания бронирования не может быть пустой")
@Future(message = "Дата окончания бронирования должна быть в будущем")
private LocalDateTime end;

private ItemDto item;

private UserDto booker;

private BookingStatus status;

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

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
@Builder
public class BookingInputDto {
private Long itemId;
private LocalDateTime start;
private LocalDateTime end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.practicum.shareit.booking.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
@Builder(toBuilder = true)
public class BookingShortDto {
private Long id;
private LocalDateTime start;
private LocalDateTime end;
private Long bookerId;
private Long itemId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,18 @@ public ConflictException(String message) {
super(message, HttpStatus.CONFLICT);
}
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
public static class BadRequestException extends ShareItException {
public BadRequestException(String message) {
super(message, HttpStatus.BAD_REQUEST);
}
}

@ResponseStatus(HttpStatus.FORBIDDEN)
public static class ForbiddenException extends ShareItException {
public ForbiddenException(String message) {
super(message, HttpStatus.FORBIDDEN);
}
}
}
Loading