diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java index b94493d49..82f9c41f2 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,12 +1,57 @@ package ru.practicum.shareit.booking; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.BookingDTO; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.service.BookingServiceImpl; + +import java.util.List; -/** - * TODO Sprint add-bookings. - */ @RestController -@RequestMapping(path = "/bookings") +@Slf4j +@RequestMapping("/bookings") public class BookingController { + + private final BookingServiceImpl bookingService; + + @Autowired + public BookingController(BookingServiceImpl bookingService) { + this.bookingService = bookingService; + } + + @PostMapping + public BookingDTO saveBooking(@RequestHeader("X-Sharer-User-Id") Long id, + @RequestBody BookingDTO bookingDto) { + return bookingService.create(id, bookingDto); + } + + @GetMapping("/{bookingId}") + public Booking findBookingById(@RequestHeader("X-Sharer-User-Id") Long id, + @PathVariable Long bookingId) { + return bookingService.findBookingById(id, bookingId); + } + + @GetMapping + public List findAllBookingsById(@RequestParam(defaultValue = "ALL") String state, + @RequestHeader("X-Sharer-User-Id") Long id) { + + return bookingService.findBookingByIdAndStatus(state, id); + } + + @PatchMapping("/{bookingId}") + public BookingDTO confirmOrRejectBooking(@RequestHeader("X-Sharer-User-Id") Long id, + @PathVariable Long bookingId, + @RequestParam Boolean approved) { + return bookingService.confirmOrRejectBooking(id, bookingId, approved); + } + + @GetMapping("/owner") + public List findAllOwnersBookings(@RequestParam(defaultValue = "ALL") String state, + @RequestHeader("X-Sharer-User-Id") Long id) { + + return bookingService.findAllOwnersBookings(state, id); + } + } diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java new file mode 100644 index 000000000..09a480f9d --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.booking; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.dto.BookingDTO; + +@Component +public class BookingMapper { + + public static BookingDTO toBookingDto(Booking booking) { + return BookingDTO.builder() + .id(booking.getId()) + .start(booking.getStart()) + .end(booking.getEnd()) + .item(booking.getItem()) + .booker(booking.getBooker()) + .bookingStatus(booking.getStatus()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingStatus.java b/src/main/java/ru/practicum/shareit/booking/BookingStatus.java new file mode 100644 index 000000000..e96eb527a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingStatus.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.booking; + + +public enum BookingStatus { + WAITING, + APPROVED, + REJECTED, + CANCELED, + ALL, + CURRENT, + PAST, + FUTURE +} diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDTO.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTO.java new file mode 100644 index 000000000..a8fa68ba7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDTO.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.booking.BookingStatus; + +import java.time.LocalDateTime; + +@Data +@Builder +@Getter +@Setter +public class BookingDTO { + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private ru.practicum.shareit.item.model.Item item; + private ru.practicum.shareit.user.model.User booker; + private User owner; + private BookingStatus bookingStatus; + + @Data + @Builder + @Getter + @Setter + public static class Item { + private Long id; + private String name; + private String description; + private boolean available; + private Long requestId; + } + + @Data + @Builder + @Getter + @Setter + public static class User { + private Long id; + private String name; + private String email; + } +} \ 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 new file mode 100644 index 000000000..ca7a34b3b --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -0,0 +1,56 @@ +package ru.practicum.shareit.booking.model; + +import lombok.*; +import org.hibernate.Hibernate; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "bookings", schema = "PUBLIC") +@Getter +@Setter +@ToString +@RequiredArgsConstructor +public class Booking { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "booking_id") + private Long id; + + @Column(name = "start_date", nullable = false) + private LocalDateTime start; + + @Column(name = "end_date", nullable = false) + private LocalDateTime end; + + @ManyToOne + @JoinColumn(name = "item_id", nullable = false) + private Item item; + + @ManyToOne + @JoinColumn(name = "booker_id", nullable = false) + private User booker; + + @Column(name = "status", nullable = false) + @Enumerated(EnumType.STRING) + private BookingStatus status = BookingStatus.WAITING; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + Booking booking = (Booking) o; + return id != null && Objects.equals(id, booking.id); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java new file mode 100644 index 000000000..ed167cd36 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -0,0 +1,56 @@ +package ru.practicum.shareit.booking.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.model.Booking; + +import java.util.List; + +public interface BookingRepository extends JpaRepository { + + @Query("SELECT b FROM Booking b WHERE b.booker.id = ?1") + List findBookingsByBookerId(Long id); + + @Query("SELECT b FROM Booking b WHERE b.booker.id = ?1 AND" + + " b.start < CURRENT_TIMESTAMP AND b.end > CURRENT_TIMESTAMP") + List findBookingsByBookerIdWithCurrentStatus(Long id); + + @Query("SELECT b FROM Booking b WHERE b.booker.id = ?1 AND" + + " b.start < CURRENT_TIMESTAMP AND b.end < CURRENT_TIMESTAMP") + List findBookingsByBookerIdWithPastStatus(Long id); + + @Query("SELECT b FROM Booking b WHERE b.booker.id = ?1 AND" + " b.start > CURRENT_TIMESTAMP ") + List findBookingsByBookerIdWithFutureStatus(Long id); + + @Query("SELECT b FROM Booking b WHERE b.booker.id = ?1 AND b.status = ?2") + List findBookingsByBookerIdWithWaitingOrRejectStatus(Long id, BookingStatus status); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id IN (SELECT it FROM Item AS it WHERE it.owner.id = ?1) " + + "AND b.status = ?2 ") + List findAllOwnersBookingsWithStatus(Long id, BookingStatus status); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id IN (SELECT it FROM Item AS it WHERE it.owner.id = ?1) ") + List findAllOwnersBookings(Long id); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id IN (SELECT it FROM Item AS it WHERE it.owner.id = ?1) " + + "AND b.start > CURRENT_TIMESTAMP ") + List findAllOwnersBookingsWithFutureStatus(Long id); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id IN (SELECT it FROM Item AS it WHERE it.owner.id = ?1) " + + "AND b.start < CURRENT_TIMESTAMP AND b.end > CURRENT_TIMESTAMP ") + List findAllOwnersBookingsWithCurrentStatus(Long id); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id IN (SELECT it FROM Item AS it WHERE it.owner.id = ?1) " + + "AND b.end < CURRENT_TIMESTAMP ") + List findAllOwnersBookingsWithPastStatus(Long id); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id = ?1") + List findAllItemBookings(Long id); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id = ?1 AND b.start < CURRENT_TIMESTAMP ") + List findAllItemBookingsPast(Long id); + + @Query(value = "SELECT b FROM Booking AS b WHERE b.item.id = ?1 AND b.start > CURRENT_TIMESTAMP ") + List findAllItemBookingsFuture(Long id); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java new file mode 100644 index 000000000..b999fa523 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.booking.service; + +import ru.practicum.shareit.booking.dto.BookingDTO; +import ru.practicum.shareit.booking.model.Booking; + +import java.util.List; + +public interface BookingService { + + BookingDTO create(Long id, BookingDTO dto); + + Booking findBookingById(Long id, Long bId); + + BookingDTO confirmOrRejectBooking(Long id, Long bId, Boolean approved); + + List findBookingByIdAndStatus(String state, Long id); + + List findAllOwnersBookings(String state, Long id); + +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java new file mode 100644 index 000000000..04b55f0e0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -0,0 +1,192 @@ +package ru.practicum.shareit.booking.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.BookingMapper; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingDTO; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.exceptions.InvalidParameterException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.exceptions.ValidationException; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Service +public class BookingServiceImpl implements BookingService { + + private final BookingRepository bRepo; + private final ItemRepository iRepo; + private final UserRepository uRepo; + + @Autowired + public BookingServiceImpl(BookingRepository bRepo, ItemRepository iRepo, UserRepository uRepo) { + this.bRepo = bRepo; + this.iRepo = iRepo; + this.uRepo = uRepo; + } + + @Override + public BookingDTO create(Long id, BookingDTO dto) { + Booking booking = new Booking(); + if (validateItem(id, dto)) { + booking.setItem(iRepo.getReferenceById(dto.getItem().getId())); + booking.setStart(dto.getStart()); + booking.setEnd(dto.getEnd()); + booking.setBooker(uRepo.getReferenceById(id)); + } + return BookingMapper.toBookingDto(bRepo.save(booking)); + } + + private boolean validateItem(Long id, BookingDTO dto) { + + if (!iRepo.existsById(dto.getItem().getId())) { + throw new NotFoundException("Товар не найден"); + } + if (id.equals(iRepo.getReferenceById(dto.getItem().getId()).getOwner().getId())) { + throw new NotFoundException("Невозможно выполнить операцию"); + } + if (dto.getEnd().isBefore(LocalDateTime.now())) { + throw new InvalidParameterException("Дата завершения не может находиться в прошлом"); + } + if (dto.getEnd().isBefore(dto.getStart())) { + throw new InvalidParameterException("Дата завершения не может быть раньше даты начала"); + } + if (dto.getStart().isBefore(LocalDateTime.now())) { + throw new InvalidParameterException("Дата начала не может быть в прошлом"); + } + if (!uRepo.existsById(id)) { + throw new NotFoundException("Пользователь не найден"); + } + if (iRepo.existsById(dto.getItem().getId())) { + if (iRepo.getReferenceById(dto.getItem().getId()).getIsAvailable() == Boolean.FALSE) { + throw new InvalidParameterException("Товар не доступен"); + } + } + return true; + } + + private void validateUser(Long id) { + if (!uRepo.existsById(id)) { + throw new NotFoundException("Пользователь не найден"); + } + } + + private void validateBooking(Long bookingId) { + if (!bRepo.existsById(bookingId)) { + throw new NotFoundException("Бронирование не найдено"); + } + } + + private void validateState(String state) { + if (!state.equals(BookingStatus.ALL.name()) && !state.equals(BookingStatus.REJECTED.name()) + && !state.equals(BookingStatus.WAITING.name()) && !state.equals(BookingStatus.CURRENT.name()) + && !state.equals(BookingStatus.APPROVED.name()) && !state.equals(BookingStatus.CANCELED.name()) + && !state.equals(BookingStatus.PAST.name()) && !state.equals(BookingStatus.FUTURE.name())) { + throw new ValidationException("Неизвестный статус"); + } + } + + + @Override + public Booking findBookingById(Long id, Long bId) { + validateUser(id); + + Booking booking = bRepo.findById(bId).orElseThrow(() -> new NotFoundException("Бронирование не найдено")); + + if (!Objects.equals(booking.getBooker().getId(), id) + && !Objects.equals(booking.getItem().getOwner().getId(), id)) { + throw new InvalidParameterException("Ошибка доступа"); + } + + return booking; + } + + @Override + public BookingDTO confirmOrRejectBooking(Long id, Long bId, Boolean approved) { + + validateBooking(bId); + + if (bRepo.getReferenceById(bId).getBooker().getId().equals(id) + && approved && bRepo.getReferenceById(bId).getId().equals(bId)) { + throw new NotFoundException("Товар не найден"); + } + if (approved && bRepo.getReferenceById(bId).getStatus().equals(BookingStatus.APPROVED) + && iRepo.getReferenceById(bRepo.getReferenceById(bId) + .getItem().getId()).getOwner().getId().equals(id)) { + throw new InvalidParameterException("Бронирование уже было подтверждено"); + } + + Booking booking = bRepo.getReferenceById(bId); + + if (approved) { + booking.setStatus(BookingStatus.APPROVED); + } else { + booking.setStatus(BookingStatus.REJECTED); + } + bRepo.save(booking); + return BookingMapper.toBookingDto(booking); + } + + @Override + public List findBookingByIdAndStatus(String state, Long id) { + validateUser(id); + validateState(state); + + List result = new ArrayList<>(); + + BookingStatus status = BookingStatus.valueOf(state); + + if (status.equals(BookingStatus.ALL)) { + result.addAll(bRepo.findBookingsByBookerId(id)); + + } else if (status.equals(BookingStatus.CURRENT)) { + result.addAll(bRepo.findBookingsByBookerIdWithCurrentStatus(id)); + + } else if (status.equals(BookingStatus.PAST)) { + result.addAll(bRepo.findBookingsByBookerIdWithPastStatus(id)); + + } else if (status.equals(BookingStatus.FUTURE)) { + result.addAll(bRepo.findBookingsByBookerIdWithFutureStatus(id)); + + } else if (status.equals(BookingStatus.WAITING)) { + result.addAll(bRepo.findBookingsByBookerIdWithWaitingOrRejectStatus(id, BookingStatus.WAITING)); + + } else if (status.equals(BookingStatus.REJECTED)) { + result.addAll(bRepo.findBookingsByBookerIdWithWaitingOrRejectStatus(id, BookingStatus.REJECTED)); + } + return result; + } + + @Override + public List findAllOwnersBookings(String state, Long id) { + validateUser(id); + validateState(state); + + List result = new ArrayList<>(); + + BookingStatus status = BookingStatus.valueOf(state); + + switch (status) { + case ALL: + result.addAll(bRepo.findAllOwnersBookings(id)); + break; + case FUTURE: + result.addAll(bRepo.findAllOwnersBookingsWithFutureStatus(id)); + break; + case CURRENT: + result.addAll(bRepo.findAllOwnersBookingsWithCurrentStatus(id)); + break; + case PAST: + result.addAll(bRepo.findAllOwnersBookingsWithPastStatus(id)); + break; + } + return result; + } +} diff --git a/src/main/java/ru/practicum/shareit/comment/Comment.java b/src/main/java/ru/practicum/shareit/comment/Comment.java new file mode 100644 index 000000000..15e8ba6c1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/comment/Comment.java @@ -0,0 +1,50 @@ +package ru.practicum.shareit.comment; + +import lombok.*; +import org.hibernate.Hibernate; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "comments", schema = "PUBLIC") +@Getter +@Setter +@ToString +@RequiredArgsConstructor +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "comment_id") + private Long id; + + @Column(name = "text", nullable = false) + private String text; + + @ManyToOne + @JoinColumn(name = "item_id", nullable = false) + private Item item; + + @ManyToOne + @JoinColumn(name = "author_id", nullable = false) + private User author; + + @Column(name = "created", nullable = false) + private LocalDateTime created = LocalDateTime.now(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + Comment comment = (Comment) o; + return id != null && Objects.equals(id, comment.id); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/comment/CommentDTO.java b/src/main/java/ru/practicum/shareit/comment/CommentDTO.java new file mode 100644 index 000000000..8061010b0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/comment/CommentDTO.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.comment; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; +import ru.practicum.shareit.item.dto.ItemDTO; +import ru.practicum.shareit.user.dto.UserDTO; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CommentDTO { + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long id; + @JsonInclude(JsonInclude.Include.NON_NULL) + private String text; + @JsonInclude(JsonInclude.Include.NON_NULL) + private ItemDTO item; + @JsonInclude(JsonInclude.Include.NON_NULL) + private UserDTO author; + private String authorName; + @JsonInclude(JsonInclude.Include.NON_NULL) + private LocalDateTime created; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/comment/CommentMapper.java b/src/main/java/ru/practicum/shareit/comment/CommentMapper.java new file mode 100644 index 000000000..00101e7df --- /dev/null +++ b/src/main/java/ru/practicum/shareit/comment/CommentMapper.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.comment; + +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.user.UserMapper; + +import java.util.HashSet; +import java.util.Set; + +public class CommentMapper { + + public static Comment toComment(CommentDTO commentDto) { + Comment comment = new Comment(); + comment.setId(commentDto.getId()); + comment.setText(commentDto.getText()); + comment.setItem(ItemMapper.toItem(commentDto.getItem())); + comment.setCreated(commentDto.getCreated()); + comment.setAuthor(UserMapper.toUser(commentDto.getAuthor())); + return comment; + } + + public static CommentDTO toCommentDto(Comment comment) { + CommentDTO commentDto = new CommentDTO(); + commentDto.setId(comment.getId()); + commentDto.setText(comment.getText()); + commentDto.setItem(ItemMapper.toIDto(comment.getItem())); + commentDto.setCreated(comment.getCreated()); + commentDto.setAuthor(UserMapper.toUserDto(comment.getAuthor())); + return commentDto; + } + + public static Set toCommentDtos(Set comments) { + Set dtos = new HashSet<>(); + for (Comment comment : comments) { + dtos.add(toCommentDto(comment)); + } + return dtos; + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/comment/CommentRepository.java b/src/main/java/ru/practicum/shareit/comment/CommentRepository.java new file mode 100644 index 000000000..545aa9eff --- /dev/null +++ b/src/main/java/ru/practicum/shareit/comment/CommentRepository.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.comment; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Set; + +public interface CommentRepository extends JpaRepository { + + @Query(value = "SELECT c FROM Comment AS c WHERE c.item.id = ?1") + Set findAllItemComments(Long id); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java new file mode 100644 index 000000000..210a70553 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + + +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleUserNotFoundException(final NotFoundException e) { + return new ErrorResponse(e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleValidationException(final ValidationException e) { + return new ErrorResponse(e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleInvalidParameterException(final InvalidParameterException e) { + return new ErrorResponse(e.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exceptions/ErrorResponse.java b/src/main/java/ru/practicum/shareit/exceptions/ErrorResponse.java new file mode 100644 index 000000000..b9de1b941 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exceptions/ErrorResponse.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ErrorResponse { + private String error; + +} diff --git a/src/main/java/ru/practicum/shareit/exceptions/InvalidParameterException.java b/src/main/java/ru/practicum/shareit/exceptions/InvalidParameterException.java new file mode 100644 index 000000000..20f3dadc2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exceptions/InvalidParameterException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exceptions; + +public class InvalidParameterException extends RuntimeException { + public InvalidParameterException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java b/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java new file mode 100644 index 000000000..c58fd6fda --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exceptions; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + log.error("{}", message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/exceptions/ValidationException.java b/src/main/java/ru/practicum/shareit/exceptions/ValidationException.java new file mode 100644 index 000000000..b9d3b3adf --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exceptions/ValidationException.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.exceptions; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + log.error("{}", message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index bb17668ba..55865c94a 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,12 +1,61 @@ package ru.practicum.shareit.item; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.comment.CommentDTO; +import ru.practicum.shareit.item.dto.ItemDTO; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.service.ItemServiceImpl; + +import java.util.Collection; +import java.util.List; -/** - * TODO Sprint add-controllers. - */ @RestController +@Slf4j @RequestMapping("/items") public class ItemController { -} + private final ItemServiceImpl itemService; + + @Autowired + public ItemController(ItemServiceImpl itemService) { + this.itemService = itemService; + } + + @PostMapping + public ItemDTO createItem(@RequestHeader("X-Sharer-User-Id") Long id, + @RequestBody ItemDTO itemDto) { + return itemService.createItem(id, itemDto); + } + + @PatchMapping("/{itemId}") + public ItemDTO updateItem(@RequestHeader("X-Sharer-User-Id") Long id, @PathVariable Long itemId, + @RequestBody ItemDTO itemDto) { + return itemService.updateItem(itemService.patchItem(itemDto, itemId, id)); + } + + @GetMapping("/{itemId}") + public ItemDTO findItemById(@RequestHeader("X-Sharer-User-Id") Long id, + @PathVariable Long itemId) { + return itemService.findItemById(id, itemId); + } + + @GetMapping + public List findAllOwnersItems(@RequestHeader("X-Sharer-User-Id") Long id) { + return itemService.findAllItemsByOwner(id); + } + + @GetMapping("/search") + public Collection findItemByString(@RequestParam String text) { + return itemService.getAllItemsByString(text); + } + + @PostMapping("/{itemId}/comment") + public CommentDTO postComment(@RequestHeader("X-Sharer-User-Id") Long id, + @PathVariable Long itemId, + @RequestBody CommentDTO commentDto) { + return itemService.postComment(id, itemId, commentDto); + } + + +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/ItemMapper.java new file mode 100644 index 000000000..c2df6a7fb --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -0,0 +1,54 @@ +package ru.practicum.shareit.item; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.dto.ItemDTO; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.UserMapper; + +@Component +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ItemMapper { + + public static Item toItem(ItemDTO itemDto) { + Item item = new Item(); + item.setId(itemDto.getId()); + item.setName(itemDto.getName()); + item.setDescription(itemDto.getDescription()); + item.setIsAvailable(itemDto.getIsAvailable()); + item.setOwner(UserMapper.toUser(itemDto.getOwner())); + item.setRequestId(itemDto.getRequestId()); + return item; + } + + public static ItemDTO toIDto(Item item) { + ItemDTO itemDto = new ItemDTO(); + itemDto.setId(item.getId()); + itemDto.setName(item.getName()); + itemDto.setDescription(item.getDescription()); + itemDto.setIsAvailable(item.getIsAvailable()); + itemDto.setOwner(UserMapper.toUserDto(item.getOwner())); + itemDto.setRequestId(item.getRequestId()); + return itemDto; + } + + /* public static ItemDTOBooking toItemDtoBooking(Item item) { + ItemDTOBooking iDtoBooking = new ItemDTOBooking(); + iDtoBooking.setId(item.getId()); + iDtoBooking.setName(item.getName()); + iDtoBooking.setDescription(item.getDescription()); + iDtoBooking.setIsAvailable(item.getIsAvailable()); + return iDtoBooking; + } + + public static List toItemBookingDtos(List items) { + List temp = new ArrayList<>(); + for (Item item : items) { + temp.add(toItemDtoBooking(item)); + } + return temp; + } + + */ +} \ 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 new file mode 100644 index 000000000..d94a121c2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDTO.java @@ -0,0 +1,75 @@ +package ru.practicum.shareit.item.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.booking.dto.BookingDTO; +import ru.practicum.shareit.comment.CommentDTO; +import ru.practicum.shareit.user.dto.UserDTO; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemDTO { + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long id; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private String name; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private String description; + + @NotNull + @JsonProperty(value = "available") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean isAvailable; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private UserDTO owner; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long requestId; + + private BookingDTO lastBooking; + + private BookingDTO nextBooking; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Set comments = new HashSet<>(); + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class User { + private Long id; + private String name; + private String email; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Booking { + private Long id; + private Long bookerId; + private LocalDateTime start; + private LocalDateTime end; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Comment { + Long id; + String text; + String authorName; + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDTOBooking.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDTOBooking.java new file mode 100644 index 000000000..8e435b6ae --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDTOBooking.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.item.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.booking.model.Booking; + +import javax.validation.constraints.NotNull; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ItemDTOBooking { + private Long id; + private String name; + private String description; + @NotNull + @JsonProperty(value = "available") + private Boolean isAvailable; + private Booking lastBooking; + private Booking nextBooking; +} 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 44eb73ddc..6765e8c08 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,7 +1,69 @@ package ru.practicum.shareit.item.model; -/** - * TODO Sprint add-controllers. - */ +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import org.hibernate.Hibernate; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.comment.Comment; +import ru.practicum.shareit.user.model.User; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; + + +@Table(name = "items") +@Getter +@Setter +@ToString +@RequiredArgsConstructor +@Entity public class Item { -} + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "item_id") + private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "description", nullable = false) + private String description; + + @NotNull + @JsonProperty(value = "available") + @Column(name = "available", nullable = false) + private Boolean isAvailable; + + @ManyToOne + @JoinColumn(name = "owner_id", nullable = false) + private User owner; + + @Column(name = "request_id") + private Long requestId; + + @Transient + private Booking lastBooking; + @Transient + private Booking nextBooking; + @OneToMany() + @JoinColumn(name = "item_id") + @ToString.Exclude + Collection comments = new ArrayList<>(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + Item item = (Item) o; + return id != null && Objects.equals(id, item.id); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java new file mode 100644 index 000000000..5240276db --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.shareit.item.dto.ItemDTO; +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; + +public interface ItemRepository extends JpaRepository { + + @Query(value = "SELECT i FROM Item i WHERE i.owner.id = ?1") + Collection findAllItemsByOwner(Long id); + + @Query("select i from Item i" + " where upper(i.name) like upper(concat('%', ?1, '%'))" + " or upper(i.description) like upper(concat('%', ?1, '%'))") + Collection search(String text); +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java new file mode 100644 index 000000000..8e1ce7e38 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java @@ -0,0 +1,97 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +public class ItemRepositoryImpl implements ItemRepository { + private static long iId = 0; + + private static Long generateId() { + iId++; + return iId; + } + + private final Map items = new HashMap<>(); + + @Override + public Item create(Long uId, Item item, User user) { + item.setId(generateId()); + item.setOwner(user); + items.put(item.getId(), item); + return item; + } + + @Override + public Item findById(Long id) { + return items.get(id); + } + + @Override + public Collection findByUserId(Long id) { + return findAll() + .values() + .stream() + .filter(i -> Objects.equals(i.getOwner().getId(), id)) + .collect(Collectors.toList()); + } + + @Override + public Map findAll() { + return items; + } + + @Override + public Item update(Long id, Item item) { + Item itemUpdated = findById(id); + + if (item.getName() != null) { + itemUpdated.setName(item.getName()); + } + + if (item.getDescription() != null) { + itemUpdated.setDescription(item.getDescription()); + } + + if (item.getAvailable() != null) { + itemUpdated.setAvailable(item.getAvailable()); + } + + return itemUpdated; + } + + @Override + public Long delete(Long id) { + return items.remove(id).getId(); + } + + @Override + public Collection search(String text) { + return findAll() + .values() + .stream() + .filter(i -> (i.getName().toLowerCase().contains(text.toLowerCase()) + || i.getDescription().toLowerCase().contains(text.toLowerCase())) + && i.getAvailable()).collect(Collectors.toList()); + } + + @Override + public boolean checkOwner(Long uId, Long iId) { + return !items.get(iId).getOwner().getId().equals(uId); + } + + @Override + public void checkItemId(Long itemId) throws NotFoundException { + if (!findAll().containsKey(itemId)) { + throw new NotFoundException("Пользователя с таким id не существует"); + } + } +} \ 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 new file mode 100644 index 000000000..f6b33415e --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item.service; + +import ru.practicum.shareit.comment.CommentDTO; +import ru.practicum.shareit.item.dto.ItemDTO; +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; +import java.util.List; + +public interface ItemService { + List findAllItemsByOwner(Long id); + + Collection getAllItemsByString(String someText); + + ItemDTO patchItem(ItemDTO itemDto, Long itemId, Long id); + + ItemDTO findItemById(Long userId, Long itemId); + + CommentDTO postComment(Long userId, Long itemId, CommentDTO commentDto); + } \ 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 new file mode 100644 index 000000000..270dd7829 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -0,0 +1,224 @@ +package ru.practicum.shareit.item.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import ru.practicum.shareit.booking.BookingMapper; +import ru.practicum.shareit.booking.dto.BookingDTO; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.comment.Comment; +import ru.practicum.shareit.comment.CommentDTO; +import ru.practicum.shareit.comment.CommentMapper; +import ru.practicum.shareit.comment.CommentRepository; +import ru.practicum.shareit.exceptions.InvalidParameterException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.dto.ItemDTO; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.user.service.UserServiceImpl; + +import java.time.LocalDateTime; +import java.util.*; + +@Service +public class ItemServiceImpl implements ItemService { + + private final ItemRepository itemRepository; + private final UserServiceImpl userService; + private final BookingRepository bookingRepository; + private final UserRepository userRepository; + private final CommentRepository commentRepository; + + @Autowired + public ItemServiceImpl(ItemRepository itemRepository, UserServiceImpl userService, BookingRepository bookingRepository, UserRepository userRepository, CommentRepository commentRepository) { + this.itemRepository = itemRepository; + this.userService = userService; + this.bookingRepository = bookingRepository; + this.userRepository = userRepository; + this.commentRepository = commentRepository; + } + + public ItemDTO createItem(Long id, ItemDTO itemDto) { + validateItemDto(itemDto); + itemDto.setOwner(userService.findUserById(id)); + // return itemDto; + return ItemMapper.toIDto(itemRepository.save(ItemMapper.toItem(itemDto))); + } + + public ItemDTO updateItem(ItemDTO itemDto) { + Item temp = itemRepository.getReferenceById(itemDto.getId()); + if (itemDto.getName() != null && !itemDto.getName().equals("")) { + temp.setName(itemDto.getName()); + } + if (itemDto.getIsAvailable() != null) { + temp.setIsAvailable(itemDto.getIsAvailable()); + } + if (itemDto.getDescription() != null && !itemDto.getDescription().equals("")) { + temp.setDescription(itemDto.getDescription()); + } + if (itemDto.getOwner().getId() != null && itemDto.getOwner().getId() != 0) { + temp.setOwner(UserMapper.toUser(itemDto.getOwner())); + } + if (itemDto.getRequestId() != null && itemDto.getRequestId() != 0) { + temp.setRequestId(itemDto.getRequestId()); + } + return ItemMapper.toIDto(itemRepository.save(temp)); + } + + public ItemDTO findItemById(Long userId, Long itemId) { + validateItem(itemId); + ItemDTO itemDto = ItemMapper.toIDto(itemRepository.getReferenceById(itemId)); + Set comments = CommentMapper.toCommentDtos(commentRepository.findAllItemComments(itemId)); + for (CommentDTO commentDto : comments) { + itemDto.getComments().add(commentDto); + for (CommentDTO commentDtoName : itemDto.getComments()) { + commentDtoName.setAuthorName(commentDtoName.getAuthor().getName()); + } + } + + if (Objects.equals(itemRepository.getReferenceById(itemId).getOwner().getId(), userId)) { + List bookingPast = bookingRepository.findAllItemBookingsPast(itemId); + if (bookingPast.size() != 0) { + bookingPast.sort(Comparator.comparing(Booking::getStart).reversed()); + BookingDTO bookingDtoPast = BookingMapper.toBookingDto(bookingPast.get(0)); + bookingDtoPast.setBooker(bookingDtoPast.getBooker()); + bookingDtoPast.setBooker(null); + itemDto.setLastBooking(bookingDtoPast); + } + List bookingFuture = bookingRepository.findAllItemBookingsFuture(itemId); + if (bookingFuture.size() != 0) { + bookingFuture.sort(Comparator.comparing(Booking::getStart)); + BookingDTO bookingDtoFuture = BookingMapper.toBookingDto(bookingFuture.get(0)); + bookingDtoFuture.setBooker(bookingDtoFuture.getBooker()); + bookingDtoFuture.setBooker(null); + itemDto.setNextBooking(bookingDtoFuture); + } + } + return itemDto; + } + + @Override + public List findAllItemsByOwner(Long id) { + validateUser(id); + // List itemDtoList = (List) ItemMapper.toIDto((Item) itemRepository.findAllItemsByOwner(id)); + List itemDtoList = new ArrayList<>(); + itemDtoList.addAll(itemRepository.findAllItemsByOwner(id)); + + for (ItemDTO itemDto : itemDtoList) { + List bookingPast = bookingRepository.findAllItemBookingsPast(itemDto.getId()); + if (bookingPast.size() != 0) { + bookingPast.sort(Comparator.comparing(Booking::getStart).reversed()); + BookingDTO bookingDtoPast = BookingMapper.toBookingDto(bookingPast.get(0)); + bookingDtoPast.setBooker(bookingDtoPast.getBooker()); + bookingDtoPast.setBooker(null); + itemDto.setLastBooking(bookingDtoPast); + } + List bookingFuture = bookingRepository.findAllItemBookingsFuture(itemDto.getId()); + if (bookingFuture.size() != 0) { + bookingFuture.sort(Comparator.comparing(Booking::getStart)); + BookingDTO bookingDtoFuture = BookingMapper.toBookingDto(bookingFuture.get(0)); + bookingDtoFuture.setBooker(bookingDtoFuture.getBooker()); + bookingDtoFuture.setBooker(null); + itemDto.setNextBooking(bookingDtoFuture); + } + } + itemDtoList.sort(Comparator.comparing(ItemDTO::getId)); + return itemDtoList; + } + + @Override + public Collection getAllItemsByString(String text) { + if (!StringUtils.hasText(text)) { + return Collections.emptyList(); + } + + return itemRepository.search(text); + } + + @Override + public ItemDTO patchItem(ItemDTO itemDto, Long itemId, Long id) { + if (findItemById(id, itemId) != null) { + if (!Objects.equals(findItemById(id, itemId).getOwner().getId(), id)) { + throw new NotFoundException("Данный товар принадлежит другому пользователю"); + } + } + itemDto.setId(itemId); + + if (findItemById(id, itemDto.getId()) == null) { + throw new NotFoundException("Такого товара нет"); + } + ItemDTO patchedItem = findItemById(id, itemDto.getId()); + if (itemDto.getName() != null) { + patchedItem.setName(itemDto.getName()); + } + if (itemDto.getDescription() != null) { + patchedItem.setDescription(itemDto.getDescription()); + } + if (itemDto.getIsAvailable() != null) { + patchedItem.setIsAvailable(itemDto.getIsAvailable()); + } + return patchedItem; + } + + public CommentDTO postComment(Long userId, Long itemId, CommentDTO commentDto) { + validateComment(commentDto); + validateUser(userId); + validateItem(itemId); + + List bookings = bookingRepository.findAllItemBookings(itemId); + for (Booking booking : bookings) { + if (!Objects.equals(booking.getBooker().getId(), userId)) { + throw new InvalidParameterException("Проверьте заданные параметры"); + } else { + if (booking.getEnd().isBefore(LocalDateTime.now())) { + commentDto.setItem(ItemMapper.toIDto(itemRepository.getReferenceById(itemId))); + commentDto.setAuthor(UserMapper.toUserDto(userRepository.getReferenceById(userId))); + commentDto.setCreated(LocalDateTime.now()); + Comment commentTemp = commentRepository.save(CommentMapper.toComment(commentDto)); + CommentDTO commentTempDto = CommentMapper.toCommentDto(commentTemp); + User user = userRepository.getReferenceById(userId); + commentTempDto.setAuthorName(user.getName()); + commentTempDto.setAuthor(null); + commentTempDto.setItem(null); + return commentTempDto; + } else { + throw new InvalidParameterException("Проверьте заданные параметры"); + } + } + } + return null; + } + + private void validateUser(Long id) { + if (!userRepository.existsById(id)) { + throw new NotFoundException("Пользователь не найден"); + } + } + + private void validateItem(Long itemId) { + if (!itemRepository.existsById(itemId)) { + throw new NotFoundException("Товар не найден"); + } + } + + private void validateItemDto(ItemDTO itemDto) { + if (itemDto.getIsAvailable() == null) { + throw new InvalidParameterException("Укажите доступность товара"); + } else if (itemDto.getName() == null || itemDto.getName().equals("")) { + throw new InvalidParameterException("Название товара не может быть пустым"); + } else if (itemDto.getDescription() == null || itemDto.getDescription().equals("")) { + throw new InvalidParameterException("Описание товара не может юыть пустым"); + } + } + + private void validateComment(CommentDTO commentDto) { + if (commentDto.getText().isEmpty() || commentDto.getText().isBlank()) { + throw new InvalidParameterException("Комментарий не может юыть пусьым"); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java index 064e2e9c6..f9e914e51 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -3,10 +3,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -/** - * TODO Sprint add-item-requests. - */ @RestController @RequestMapping(path = "/requests") public class ItemRequestController { -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/RequestMapper.java b/src/main/java/ru/practicum/shareit/request/RequestMapper.java new file mode 100644 index 000000000..ce20aa667 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/RequestMapper.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.request; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.request.dto.ItemRequestDTO; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + +@Component +public class RequestMapper { + public ItemRequestDTO toItemDto(ItemRequest item) { + return ItemRequestDTO.builder() + .id(item.getId()) + .description(item.getDescription()) + .requester(toUserItemRequest(item.getRequester())) + .created(item.getCreated()) + .build(); + } + + public ItemRequest toItem(ItemRequestDTO itemDto) { + return new ItemRequest( + itemDto.getId(), + itemDto.getDescription(), + toUser(itemDto.getRequester()), + itemDto.getCreated() + ); + } + + private ItemRequestDTO.User toUserItemRequest(User user) { + return ItemRequestDTO.User.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + private User toUser(ItemRequestDTO.User bookingUser) { + User user = new User(); + user.setId(bookingUser.getId()); + user.setName(bookingUser.getName()); + user.setEmail(bookingUser.getEmail()); + return user; + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDTO.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDTO.java new file mode 100644 index 000000000..e98db74bc --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDTO.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Builder +public class ItemRequestDTO { + + private Long id; + private String description; + private User requester; + private LocalDateTime created; + + @Data + @Builder + public static class User { + private Long id; + private String name; + private String email; + } +} \ 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 new file mode 100644 index 000000000..5bba11a66 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit.request.model; + +import lombok.*; +import org.hibernate.Hibernate; +import ru.practicum.shareit.user.model.User; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Getter +@Setter +@ToString +@RequiredArgsConstructor +@Table(name = "requests") +@AllArgsConstructor +public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String description; + @ManyToOne + @JoinColumn(name = "requester_id") + private User requester; + private LocalDateTime created; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + ItemRequest that = (ItemRequest) o; + return id != null && Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/src/main/java/ru/practicum/shareit/user/UserController.java index 03039b9da..182c725e1 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,12 +1,50 @@ package ru.practicum.shareit.user; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -/** - * TODO Sprint add-controllers. - */ +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDTO; +import ru.practicum.shareit.user.service.UserServiceImpl; + +import java.util.List; + @RestController -@RequestMapping(path = "/users") +@Slf4j +@RequestMapping("/users") public class UserController { -} + + private final UserServiceImpl userService; + + @Autowired + public UserController(UserServiceImpl userService) { + this.userService = userService; + } + + @PostMapping + public UserDTO createUser(@RequestBody UserDTO userDto) { + return userService.createUser(userDto); + } + + @PatchMapping("/{userId}") + public UserDTO updateUser(@RequestBody UserDTO userDto, @PathVariable Long userId) { + return userService.updateUser(userService.patchUser(userDto, userId)); + } + + @GetMapping + public List getAllUsers() { + return userService.findAllUsers(); + } + + @GetMapping("/{id}") + public UserDTO getUserById(@PathVariable Long id) { + return userService.findUserById(id); + } + + @DeleteMapping("/{id}") + public void deleteUserById(@PathVariable Long id) { + userService.deleteUserById(id); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/src/main/java/ru/practicum/shareit/user/UserMapper.java new file mode 100644 index 000000000..9022c277a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -0,0 +1,35 @@ +package ru.practicum.shareit.user; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.user.dto.UserDTO; +import ru.practicum.shareit.user.model.User; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class UserMapper { + public static UserDTO toUserDto(User user) { + UserDTO userDto = new UserDTO(); + userDto.setId(user.getId()); + userDto.setName(user.getName()); + userDto.setEmail(user.getEmail()); + return userDto; + } + + public static User toUser(UserDTO userDto) { + User user = new User(); + user.setId(userDto.getId()); + user.setName(userDto.getName()); + user.setEmail(userDto.getEmail()); + return user; + } + + public static List toUserDTOs(List users) { + List tempUsers = new ArrayList<>(); + for (User user : users) { + tempUsers.add(toUserDto(user)); + } + return tempUsers; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDTO.java b/src/main/java/ru/practicum/shareit/user/dto/UserDTO.java new file mode 100644 index 000000000..79e3c73ef --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDTO.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Email; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class UserDTO { + private Long id; + private String name; + @Email + private String email; +} \ 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 new file mode 100644 index 000000000..466373555 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,42 @@ +package ru.practicum.shareit.user.model; + +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import javax.validation.constraints.Email; +import java.util.Objects; + +@Entity +@Table(name = "users") +@Getter +@Setter +@ToString +@RequiredArgsConstructor +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "email", nullable = false) + @Email + private String email; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + User user = (User) o; + return id != null && Objects.equals(id, user.id); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java new file mode 100644 index 000000000..45dfa1fc7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.user.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.user.model.User; + +public interface UserRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java new file mode 100644 index 000000000..6f003c8e9 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java @@ -0,0 +1,80 @@ +package ru.practicum.shareit.user.repository; + +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import ru.practicum.shareit.exceptions.InvalidParameterException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.exceptions.ValidationException; +import ru.practicum.shareit.user.model.User; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@Service +public class UserRepositoryImpl implements UserRepository { + + private static long id = 0; + + private static Long generateUserId() { + return ++id; + } + + private final Map users = new HashMap<>(); + + @Override + public User create(User user) { + user.setId(generateUserId()); + users.put(user.getId(), user); + return user; + } + + @Override + public User findById(Long id) { + return users.get(id); + } + + @Override + public Map findAll() { + return users; + } + + @Override + public User update(Long id, User user) { + User userUpd = findById(id); + + if (user.getEmail() != null) { + userUpd.setEmail(user.getEmail()); + } + + if (user.getName() != null) { + userUpd.setName(user.getName()); + } + + return userUpd; + } + + @Override + public Long delete(Long id) { + return users.remove(id).getId(); + } + + @Override + public void checkId(Long id) throws HttpClientErrorException.NotFound, NotFoundException { + if (!findAll().containsKey(id)) { + throw new NotFoundException("Пользователь id %d не найден"); + } + } + + @Override + public void checkEmail(String email) throws ValidationException { + if (email == null || email.isBlank()) { + throw new InvalidParameterException("Почта не может быть пустой"); + } + for (User user : findAll().values()) { + if (Objects.equals(user.getEmail(), email)) { + throw new ValidationException("Пользователь уже зарегистрирован"); + } + } + } +} diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/src/main/java/ru/practicum/shareit/user/service/UserService.java new file mode 100644 index 000000000..72946c17d --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -0,0 +1,8 @@ +package ru.practicum.shareit.user.service; + +import ru.practicum.shareit.user.dto.UserDTO; + +public interface UserService { + + UserDTO patchUser(UserDTO userDto, 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/service/UserServiceImpl.java new file mode 100644 index 000000000..56a629043 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -0,0 +1,85 @@ +package ru.practicum.shareit.user.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exceptions.InvalidParameterException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.dto.UserDTO; +import ru.practicum.shareit.user.repository.UserRepository; + +import javax.validation.ValidationException; +import java.util.List; + +@Service +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + @Autowired + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public List findAllUsers() { + return UserMapper.toUserDTOs(userRepository.findAll()); + } + + public UserDTO findUserById(Long id) { + validateUser(id); + return UserMapper.toUserDto(userRepository.getReferenceById(id)); + } + + public UserDTO createUser(UserDTO userDto) { + validateEmail(userDto); + return UserMapper.toUserDto(userRepository.save(UserMapper.toUser(userDto))); + } + + public UserDTO updateUser(UserDTO userDto) { + UserDTO temp = UserMapper.toUserDto(userRepository.getReferenceById(userDto.getId())); + if (userDto.getName() != null && !userDto.getName().equals("")) { + temp.setName(userDto.getName()); + } + if (userDto.getEmail() != null && !userDto.getEmail().equals("")) { + temp.setEmail(userDto.getEmail()); + } + return UserMapper.toUserDto(userRepository.save(UserMapper.toUser(temp))); + } + + public void deleteUserById(Long id) { + userRepository.deleteById(id); + } + + @Override + public UserDTO patchUser(UserDTO userDto, Long userId) { + userDto.setId(userId); + if (findUserById(userDto.getId()) == null) { + throw new NotFoundException("Пользователь не найден"); + } + UserDTO patchedUser = findUserById(userDto.getId()); + if (userDto.getName() != null) { + patchedUser.setName(userDto.getName()); + } + if (userDto.getEmail() != null) { + for (UserDTO storedUser : findAllUsers()) { + if (userDto.getEmail().equals(storedUser.getEmail())) { + throw new ValidationException("Пользователь с таекой почтой уже существует"); + } + } + patchedUser.setEmail(userDto.getEmail()); + } + return patchedUser; + } + + private void validateUser(Long id) { + if (!userRepository.existsById(id)) { + throw new NotFoundException("Пользователь не найден"); + } + } + + private void validateEmail(UserDTO userDto) { + if (userDto.getEmail() == null || !userDto.getEmail().contains("@")) { + throw new InvalidParameterException("Почта не может быть пустой"); + } + } + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c359e8d7a..b8c1f8d28 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,22 +1,10 @@ spring.jpa.hibernate.ddl-auto=none -spring.jpa.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.hibernate.show_sql=true - +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.properties.hibernate.format_sql=true spring.sql.init.mode=always -# TODO Append connection to DB - - - -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 -#--- spring.config.activate.on-profile=ci,test spring.datasource.driverClassName=org.h2.Driver spring.datasource.url=jdbc:h2:mem:shareit spring.datasource.username=test spring.datasource.password=test - -spring.h2.console.enabled=true \ 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 000000000..25347c901 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,43 @@ +CREATE TABLE IF NOT EXISTS users ( + user_id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL UNIQUE, + name VARCHAR(255) NOT NULL, + email VARCHAR(512) NOT NULL UNIQUE +); + + +CREATE TABLE IF NOT EXISTS requests ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL UNIQUE, + description VARCHAR(512) NOT NULL, + requester_id BIGINT REFERENCES users (user_id), + created TIMESTAMP WITHOUT TIME ZONE CHECK NOT NULL + +); + +CREATE TABLE IF NOT EXISTS items ( + item_id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL UNIQUE, + name VARCHAR(255) NOT NULL, + description VARCHAR(512) NOT NULL, + available BOOLEAN NOT NULL, + owner_id BIGINT REFERENCES users (user_id), + request_id BIGINT REFERENCES requests (id) +); + +CREATE TABLE IF NOT EXISTS bookings ( + booking_id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL UNIQUE, + start_date TIMESTAMP WITHOUT TIME ZONE CHECK (start_date < end_date), + end_date TIMESTAMP WITHOUT TIME ZONE CHECK (end_date > start_date), + item_id BIGINT REFERENCES items (item_id), + booker_id BIGINT REFERENCES users (user_id), + status varchar(50) CHECK (status IN ('WAITING', 'APPROVED', 'REJECTED', 'CANCELED')) +); + + + + +CREATE TABLE IF NOT EXISTS comments ( + comment_id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL UNIQUE, + text VARCHAR(512) NOT NULL, + item_id BIGINT REFERENCES items (item_id), + author_id BIGINT REFERENCES users (user_id), + created TIMESTAMP WITHOUT TIME ZONE CHECK NOT NULL +); diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/src/test/java/ru/practicum/shareit/ShareItTests.java index 4d79052f0..77f8aa7fc 100644 --- a/src/test/java/ru/practicum/shareit/ShareItTests.java +++ b/src/test/java/ru/practicum/shareit/ShareItTests.java @@ -2,8 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; @SpringBootTest +@ContextConfiguration class ShareItTests { @Test