diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..e992ee2 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,12 @@ +services: + db: + image: postgres:16.1 + container_name: postgres + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=12345 + - POSTGRES_USER=shareit + - POSTGRES_DB=shareit + - TZ=Europe/Moscow + diff --git a/pom.xml b/pom.xml index a7fbefe..a4e00c4 100644 --- a/pom.xml +++ b/pom.xml @@ -20,10 +20,10 @@ - - - - + + org.springframework.boot + spring-boot-starter-jdbc + org.springframework.boot spring-boot-starter-web @@ -49,6 +49,11 @@ runtime + + org.springframework.boot + spring-boot-starter-data-jpa + + org.projectlombok lombok diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java index 192431e..d68792e 100644 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/Booking.java @@ -1,15 +1,41 @@ package ru.practicum.shareit.booking; -import lombok.Data; +import jakarta.persistence.*; +import lombok.*; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.User; import java.time.LocalDateTime; -@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "booking") public class Booking { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "start_time", nullable = false) private LocalDateTime start; + + @Column(name = "end_time", nullable = false) private LocalDateTime end; - private Long itemId; - private Long booker; - private Status status; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "booker_id") + private User booker; + + @Enumerated(EnumType.STRING) + private BookingStatus status; + + } diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java index b94493d..99fca72 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,12 +1,51 @@ package ru.practicum.shareit.booking; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.service.BookingService; -/** - * TODO Sprint add-bookings. - */ +import java.util.List; + + +@Validated @RestController +@RequiredArgsConstructor @RequestMapping(path = "/bookings") public class BookingController { + private final BookingService bookingService; + private final BookingCreateDto createDto; + + @PostMapping + public BookingDto setBookingItem(@RequestHeader("X-Sharer-User-Id") Long userId, + @Validated @RequestBody BookingCreateDto createDto) { + createDto.normalizeTimestamps(); + return bookingService.setBookingItem(createDto, userId); + } + + @PatchMapping("/{bookingId}") + public BookingDto setBookingStatus(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long bookingId, @RequestParam boolean approved) { + return bookingService.setBookingStatus(userId, bookingId, approved); + } + + @GetMapping("/{bookingId}") + public BookingDto getBookingById(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long bookingId) { + return bookingService.getBookingById(userId, bookingId); + } + + @GetMapping + public List getBookingByBookerIdWhereTime(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "ALL") String state) { + return bookingService.getBookingByBookerIdWhereTime(userId, state); + } + + @GetMapping("/owner") + List getBookingByOwnerIdWhereTime(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "ALL") String state) { + return bookingService.getBookingByOwnerIdWhereTime(userId, state); + } + } 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 0000000..bfbccf6 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingMapper.java @@ -0,0 +1,47 @@ +package ru.practicum.shareit.booking; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.user.UserMapper; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Component +public class BookingMapper { + private final ItemMapper itemMapper; + private final UserMapper userMapper; + + public Booking bookingCreateDtoToModel(BookingCreateDto dto) { + return Booking.builder() + .start(dto.getStart()) + .end(dto.getEnd()) + .status(BookingStatus.WAITING) + .build(); + } + + public BookingDto modelToDto(Booking booking) { + return BookingDto.builder() + .id(booking.getId()) + .start(booking.getStart()) + .end(booking.getEnd()) + .item(itemMapper.modelToItemDto(booking.getItem())) + .booker(userMapper.modelToDto(booking.getBooker())) + .status(booking.getStatus()) + .build(); + } + + public List listModelToDto(List bookings) { + List bookingDto = new ArrayList<>(); + for (Booking booking : bookings) { + bookingDto.add(modelToDto(booking)); + } + return bookingDto; + } + + +} diff --git a/src/main/java/ru/practicum/shareit/booking/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/BookingRepository.java new file mode 100644 index 0000000..4838242 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingRepository.java @@ -0,0 +1,69 @@ +package ru.practicum.shareit.booking; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; + +public interface BookingRepository extends JpaRepository { + + @Query(value = """ + SELECT EXISTS ( + SELECT 1 FROM booking + WHERE item_id = :itemId + AND booker_id = :userId + AND status = 'APPROVED' + AND end_time < CURRENT_TIMESTAMP + )""", + nativeQuery = true) + boolean hasUserBookedItem(@Param("userId") Long userId, @Param("itemId") Long itemId); + + @Query(""" + SELECT COUNT(b) > 0 + FROM Booking b + WHERE b.item.id = :itemId + AND b.status IN ('APPROVED', 'WAITING') + AND ( + (b.start BETWEEN :start AND :end) OR + (b.end BETWEEN :start AND :end) OR + (b.start <= :start AND b.end >= :end) + ) + """) + boolean existsByItemIdAndTimeRange(@Param("itemId") Long itemId, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + @Query(value = """ + SELECT * FROM booking + WHERE booker_id = :bookerId + AND ( + (:state = 'ALL') OR + (:state = 'CURRENT' AND start_time <= NOW() AND end_time >= NOW()) OR + (:state = 'PAST' AND end_time < NOW()) OR + (:state = 'FUTURE' AND start_time > NOW()) OR + (:state = 'WAITING' AND status = 'WAITING') OR + (:state = 'REJECTED' AND status = 'REJECTED') + ) + ORDER BY start_time DESC""", + nativeQuery = true) + List getBookingByBookerIdWhereTime(@Param("bookerId") Long bookerId, @Param("state") String state); + + + @Query(value = """ + SELECT b.* FROM booking b + JOIN items i ON b.item_id = i.id + WHERE i.owner_id = :ownerId + AND ( + (:state = 'ALL') OR + (:state = 'CURRENT' AND NOW() BETWEEN b.start_time AND b.end_time) OR + (:state = 'PAST' AND b.end_time < NOW()) OR + (:state = 'FUTURE' AND b.start_time > NOW()) OR + (:state = b.status) + ) + ORDER BY b.start_time DESC""", + nativeQuery = true) + List getBookingByOwnerIdWhereTime(@Param("ownerId") Long ownerId, @Param("state") String state); + +} diff --git a/src/main/java/ru/practicum/shareit/booking/Status.java b/src/main/java/ru/practicum/shareit/booking/BookingStatus.java similarity index 66% rename from src/main/java/ru/practicum/shareit/booking/Status.java rename to src/main/java/ru/practicum/shareit/booking/BookingStatus.java index 2037aa5..51269e0 100644 --- a/src/main/java/ru/practicum/shareit/booking/Status.java +++ b/src/main/java/ru/practicum/shareit/booking/BookingStatus.java @@ -1,8 +1,8 @@ package ru.practicum.shareit.booking; -public enum Status { +public enum BookingStatus { WAITING, APPROVED, REJECTED, - CANCELED; + CANCELED } diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java index 7728bf4..ffa28b5 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java @@ -1,21 +1,40 @@ package ru.practicum.shareit.booking.dto; -import jakarta.validation.constraints.Null; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; import lombok.Data; -import ru.practicum.shareit.booking.Status; +import org.springframework.stereotype.Component; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +@Component @Data public class BookingCreateDto { - private Long id; - @Null + @NotNull + @FutureOrPresent private LocalDateTime start; - @Null + @NotNull + @Future private LocalDateTime end; - @Null + @NotNull private Long itemId; - @Null - private Long booker; - private Status status; + + @AssertTrue(message = "Дата окончания должна быть позже даты начала") + boolean isStartBeforeEnd() { + return start != null && + end != null && + start.isBefore(end); + } + + public void normalizeTimestamps() { + if (start != null) { + start = start.truncatedTo(ChronoUnit.SECONDS); + } + if (end != null) { + end = end.truncatedTo(ChronoUnit.SECONDS); + } + } } diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java index 9f9bd16..ccdadb4 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,16 +1,20 @@ package ru.practicum.shareit.booking.dto; +import lombok.Builder; import lombok.Data; -import ru.practicum.shareit.booking.Status; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; import java.time.LocalDateTime; +@Builder @Data -public class BookingDto { +public class BookingDto { private Long id; private LocalDateTime start; private LocalDateTime end; - private Long itemId; - private Long booker; - private Status status; + private ItemDto item; + private UserDto booker; + private BookingStatus status; } diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingUpdateDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingUpdateDto.java index 2acf2a9..0673edf 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingUpdateDto.java +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingUpdateDto.java @@ -1,8 +1,9 @@ package ru.practicum.shareit.booking.dto; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Null; import lombok.Data; -import ru.practicum.shareit.booking.Status; +import ru.practicum.shareit.booking.BookingStatus; import java.time.LocalDateTime; @@ -14,5 +15,12 @@ public class BookingUpdateDto { private LocalDateTime end; private Long itemId; private Long booker; - private Status status; + private BookingStatus bookingStatus; + + @AssertTrue(message = "Дата окончания должна быть позже даты начала") + boolean isStartBeforeEnd() { + return start != null && + end != null && + start.isBefore(end); + } } 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 0000000..36791f9 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.booking.service; + +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; + +import java.util.List; + +public interface BookingService { + BookingDto setBookingItem(BookingCreateDto createDto, long bookerId); + + BookingDto setBookingStatus(long userId, long bookingId, boolean approve); + + BookingDto getBookingById(long userId, long bookingId); + + List getBookingByBookerIdWhereTime(long booker, String state); + + List getBookingByOwnerIdWhereTime(long ownerId, String state); +} 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 0000000..b562b38 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -0,0 +1,108 @@ +package ru.practicum.shareit.booking.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.Booking; +import ru.practicum.shareit.booking.BookingMapper; +import ru.practicum.shareit.booking.BookingRepository; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.exception.BadRequestException; +import ru.practicum.shareit.exception.ConflictException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.UserRepository; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class BookingServiceImpl implements BookingService { + private final ItemRepository itemRepository; + private final UserRepository userRepository; + private final BookingMapper bookingMapper; + private final BookingRepository bookingRepository; + + @Override + public BookingDto setBookingItem(BookingCreateDto createDto, long bookerId) { + User user = checkAndReturnUser(bookerId); + Item item = checkAndReturnItem(createDto.getItemId()); + + if (!item.getAvailable()) { + throw new BadRequestException("Вещь недоступна для бронирования"); + } + if (bookingRepository.existsByItemIdAndTimeRange( + createDto.getItemId(), createDto.getStart(), createDto.getStart())) { + throw new ConflictException("Вещь уже забронирована на указанные даты"); + } + Booking booking = bookingMapper.bookingCreateDtoToModel(createDto); + booking.setItem(item); + booking.setBooker(user); + booking = bookingRepository.save(booking); + return bookingMapper.modelToDto(booking); + } + + @Override + public BookingDto setBookingStatus(long userId, long bookingId, boolean approve) { + Booking booking = checkAndReturnBooking(bookingId); + + if (booking.getItem().getOwner().getId() != userId) { + throw new BadRequestException("Изменить статус бронирования может только владелец вещи"); + } + if (approve) { + booking.setStatus(BookingStatus.APPROVED); + } else { + booking.setStatus(BookingStatus.REJECTED); + } + bookingRepository.save(booking); + return bookingMapper.modelToDto(booking); + } + + @Override + public BookingDto getBookingById(long userId, long bookingId) { + Booking booking = checkAndReturnBooking(bookingId); + long owner = booking.getItem().getOwner().getId(); + long broker = booking.getBooker().getId(); + + if (userId != owner && userId != broker) { + throw new ConflictException("Данные о бронировании доступны только автору или владельцу вещи"); + } + return bookingMapper.modelToDto(booking); + } + + @Override + public List getBookingByBookerIdWhereTime(long booker, String state) { + checkAndReturnUser(booker); + List bookings = bookingRepository.getBookingByBookerIdWhereTime(booker, state); + if (bookings.isEmpty()) { + throw new ConflictException("Данные о бронировании не найдены"); + } + return bookingMapper.listModelToDto(bookings); + } + + @Override + public List getBookingByOwnerIdWhereTime(long ownerId, String state) { + checkAndReturnUser(ownerId); + List bookings = bookingRepository.getBookingByOwnerIdWhereTime(ownerId, state); + if (bookings.isEmpty()) { + throw new ConflictException("Данные о бронировании не найдены"); + } + return bookingMapper.listModelToDto(bookings); + } + + private Booking checkAndReturnBooking(long bookingId) { + return bookingRepository.findById(bookingId).orElseThrow(() -> new NotFoundException("Booking не найден")); + } + + private Item checkAndReturnItem(long itemId) { + return itemRepository.findById(itemId).orElseThrow(() -> new NotFoundException("Item не найден")); + } + + private User checkAndReturnUser(long id) { + return userRepository.findById(id).orElseThrow(() -> new NotFoundException("User не найден")); + } + +} diff --git a/src/main/java/ru/practicum/shareit/exception/BadRequestException.java b/src/main/java/ru/practicum/shareit/exception/BadRequestException.java new file mode 100644 index 0000000..bcc18ec --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java index b134feb..3b135b6 100644 --- a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java +++ b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -33,6 +33,12 @@ public ErrorResponse handleConflictException(final ConflictException e) { return new ErrorResponse("Error", e.getMessage()); } + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleBadRequestException(final BadRequestException e) { + return new ErrorResponse("Error", e.getMessage()); + } + } diff --git a/src/main/java/ru/practicum/shareit/item/CommentMapper.java b/src/main/java/ru/practicum/shareit/item/CommentMapper.java new file mode 100644 index 0000000..3da8aa0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/CommentMapper.java @@ -0,0 +1,55 @@ +package ru.practicum.shareit.item; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.dto.CommentCreateDto; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.CommentDtoReturn; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Comment; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +@RequiredArgsConstructor +@Component +public class CommentMapper { + private final UserMapper userMapper; + + public Comment commentCreateDtoToModel(CommentCreateDto createDto) { + return Comment.builder() + .text(createDto.getText()) + .created(LocalDateTime.now()) + .build(); + } + + public CommentDtoReturn modelToReturnDto(Comment comment) { + return CommentDtoReturn.builder() + .id(comment.getId()) + .text(comment.getText()) + .authorName(comment.getAuthor().getName()) + .created(comment.getCreated()) + .build(); + } + + public CommentDto modelToDto(Comment comment) { + UserDto userDto = userMapper.modelToDto(comment.getAuthor()); + Item item = comment.getItem(); + ItemDto itemDto = ItemDto.builder() + .id(item.getId()) + .name(item.getName()) + .description(item.getDescription()) + .available(item.getAvailable()) + .owner(userDto) + .build(); + + return CommentDto.builder() + .id(comment.getId()) + .text(comment.getText()) + .item(itemDto) + .author(userDto) + .build(); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index 65d2be9..26c15e3 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -3,10 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.item.dto.ItemCreateDto; -import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemUpdateDto; -import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.dto.*; import ru.practicum.shareit.item.service.ItemService; import java.util.List; @@ -17,36 +14,38 @@ @RequestMapping("/items") public class ItemController { private final ItemService itemService; - private final ItemMapper itemMapper; + + @PostMapping("/{itemId}/comment") + public CommentDtoReturn createComment(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable long itemId, + @Validated @RequestBody CommentCreateDto createDto) { + return itemService.createComment(userId, itemId, createDto); + } @PostMapping - public ItemDto create(@RequestHeader("X-Sharer-User-Id") Long userId, - @Validated @RequestBody ItemCreateDto createDto) { - Item item = itemService.create(itemMapper.createDtoToModel(createDto, userId)); - return itemMapper.modelToItemDto(item); + public ItemDto createItem(@RequestHeader("X-Sharer-User-Id") Long userId, + @Validated @RequestBody ItemCreateDto createDto) { + return itemService.createItem(createDto, userId); } @PatchMapping("/{itemId}") public ItemDto update(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long itemId, @Validated @RequestBody ItemUpdateDto updateDto) { - Item item = itemService.update(itemMapper.updateDtoToModel(updateDto, userId, itemId)); - return itemMapper.modelToItemDto(item); + return itemService.update(updateDto, userId, itemId); } - @GetMapping("/{itemId}") - public ItemDto getItemById(@PathVariable Long itemId) { - return itemMapper.modelToItemDto(itemService.getItemById(itemId)); + public ItemCommentDto getItemById(@RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long itemId) { + return itemService.getByItemIdWithComment(userId, itemId); } @GetMapping - public List getItemByOwnerId(@RequestHeader("X-Sharer-User-Id") Long userId) { - return itemMapper.listModelToDto(itemService.getItemByOwnerId(userId)); + public List getItemByOwnerId(@RequestHeader("X-Sharer-User-Id") Long userId) { + return itemService.findAllByOwnerIdWithBookings(userId); } @GetMapping("/search") public List getItemsByNameOrDescription(@RequestParam String text) { - return itemMapper.listModelToDto(itemService.getItemsByNameOrDescription(text)); + return itemService.getItemsByNameOrDescription(text); } } diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/ItemMapper.java index a86c789..088edeb 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -1,33 +1,34 @@ package ru.practicum.shareit.item; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import ru.practicum.shareit.item.dto.ItemCreateDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.dto.ItemUpdateDto; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.dto.UserDto; import java.util.ArrayList; import java.util.List; +@RequiredArgsConstructor @Component public class ItemMapper { - public Item createDtoToModel(ItemCreateDto dto, long ownerId) { + public Item createDtoToModel(ItemCreateDto dto) { return Item.builder() .name(dto.getName()) .description(dto.getDescription()) .available(dto.getAvailable()) - .owner(ownerId) .build(); } - public Item updateDtoToModel(ItemUpdateDto dto, long ownerId, long itemId) { + public Item updateDtoToModel(ItemUpdateDto dto, long itemId) { return Item.builder() .id(itemId) .name(dto.getName()) .description(dto.getDescription()) .available(dto.getAvailable()) - .owner(ownerId) .build(); } @@ -37,7 +38,11 @@ public ItemDto modelToItemDto(Item item) { .name(item.getName()) .description(item.getDescription()) .available(item.getAvailable()) - .owner(item.getOwner()) + .owner(UserDto.builder() + .id(item.getOwner().getId()) + .name(item.getOwner().getName()) + .email(item.getOwner().getEmail()) + .build()) .build(); } diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java b/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java new file mode 100644 index 0000000..7a88676 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + + +@Getter +@Setter +public class CommentCreateDto { + @NotNull + @NotBlank + private String text; +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..59bd7f5 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.item.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +@Builder +@Getter +@Setter +public class CommentDto { + private Long id; + private String text; + private ItemDto item; + private UserDto author; + private LocalDateTime created; + + public CommentDto(Long id, String text, ItemDto item, UserDto author, LocalDateTime created) { + this.id = id; + this.text = text; + this.item = item; + this.author = author; + this.created = created; + } + +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDtoReturn.java b/src/main/java/ru/practicum/shareit/item/dto/CommentDtoReturn.java new file mode 100644 index 0000000..bf7738d --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/CommentDtoReturn.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.item.dto; + + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Builder +@Getter +@Setter +public class CommentDtoReturn { + private Long id; + private String text; + private String authorName; + private LocalDateTime created; +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemCommentDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemCommentDto.java new file mode 100644 index 0000000..714961c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemCommentDto.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Data +public class ItemCommentDto { + private Long id; + private String name; + private String description; + private Boolean available; + private UserDto owner; + private LocalDateTime lastBooking; + private LocalDateTime nextBooking; + private List comments; + + public ItemCommentDto(Long id, String name, String description, Boolean available, + UserDto owner, LocalDateTime lastBooking, LocalDateTime nextBooking) { + this.id = id; + this.name = name; + this.description = description; + this.available = available; + this.owner = owner; + this.lastBooking = lastBooking; + this.nextBooking = nextBooking; + this.comments = new ArrayList<>(); // Инициализируем пустой список + } +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java index 2cb517d..ed2d63d 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java @@ -2,9 +2,11 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; -@Data +@Getter +@Setter public class ItemCreateDto { @NotNull @NotBlank diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index b898db1..378ad89 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -1,14 +1,21 @@ package ru.practicum.shareit.item.dto; +import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.user.dto.UserDto; + @Builder -@Data +@Getter +@Setter +@AllArgsConstructor public class ItemDto { private Long id; private String name; private String description; private Boolean available; - private Long owner; + private UserDto owner; + } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java index 3d489dd..81b6409 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java @@ -1,8 +1,10 @@ package ru.practicum.shareit.item.dto; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; -@Data +@Getter +@Setter public class ItemUpdateDto { private String name; private String description; diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/src/main/java/ru/practicum/shareit/item/model/Comment.java new file mode 100644 index 0000000..1b1ccd4 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/model/Comment.java @@ -0,0 +1,34 @@ +package ru.practicum.shareit.item.model; + +import jakarta.persistence.*; +import lombok.*; +import ru.practicum.shareit.user.User; + +import java.time.LocalDateTime; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "comments") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String text; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "author_id") + private User author; + + @Column(name = "created", nullable = false) + private LocalDateTime created; +} 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 07f84c6..dc6145a 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,15 +1,33 @@ package ru.practicum.shareit.item.model; -import lombok.Builder; -import lombok.Data; +import jakarta.persistence.*; +import lombok.*; +import ru.practicum.shareit.user.User; +@AllArgsConstructor +@NoArgsConstructor @Builder -@Data +@Getter +@Setter +@ToString +@Entity +@Table(name = "items") public class Item { - private long id; - private String name; - private String description; - private Boolean available; - private long owner; - private long requestId; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(nullable = false) + private String name; + + private String description; + + @Column(nullable = false) + private Boolean available; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "owner_id") + private User owner; + + } diff --git a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java new file mode 100644 index 0000000..26a1bdd --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java @@ -0,0 +1,72 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.model.Comment; + +import java.util.Collection; +import java.util.List; + +public interface CommentRepository extends JpaRepository { + + @Query(""" + SELECT new ru.practicum.shareit.item.dto.CommentDto( + c.id, + c.text, + new ru.practicum.shareit.item.dto.ItemDto( + i.id, + i.name, + i.description, + i.available, + new ru.practicum.shareit.user.dto.UserDto( + i.owner.id, + i.owner.name, + i.owner.email + ) + ), + new ru.practicum.shareit.user.dto.UserDto( + u.id, + u.name, + u.email + ), + c.created + ) + FROM Comment c + JOIN c.item i + JOIN c.author u + WHERE c.item.id = :itemId + ORDER BY c.created DESC""") + List findByItemId(@Param("itemId") Long itemId); + + @Query(""" + SELECT new ru.practicum.shareit.item.dto.CommentDto( + c.id, + c.text, + new ru.practicum.shareit.item.dto.ItemDto( + i.id, + i.name, + i.description, + i.available, + new ru.practicum.shareit.user.dto.UserDto( + i.owner.id, + i.owner.name, + i.owner.email + ) + ), + new ru.practicum.shareit.user.dto.UserDto( + u.id, + u.name, + u.email + ), + c.created + ) + FROM Comment c + JOIN c.item i + JOIN c.author u + WHERE c.item.id IN :itemIds + ORDER BY c.created DESC""") + List findByItemIds(@Param("itemIds") Collection itemIds); + +} diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java index 1e454ac..3b7323b 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -1,19 +1,57 @@ package ru.practicum.shareit.item.repository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.practicum.shareit.item.dto.ItemCommentDto; import ru.practicum.shareit.item.model.Item; import java.util.List; - -public interface ItemRepository { - long getNextId(); - - Item create(Item item); - - Item update(Item item); - - Item getItemById(long itemId); - - List getItemByOwnerId(long ownerId); - - List getItemsByNameOrDescription(String text); +import java.util.Optional; + + +public interface ItemRepository extends JpaRepository { + + List findByOwnerId(long ownerId); + + @Query(""" + SELECT new ru.practicum.shareit.item.dto.ItemCommentDto( + i.id, + i.name, + i.description, + i.available, + new ru.practicum.shareit.user.dto.UserDto(u.id, u.name, u.email), + (SELECT MAX(b.start) FROM Booking b + WHERE b.item.id = i.id AND b.status = 'APPROVED' AND b.end < CURRENT_TIMESTAMP), + (SELECT MIN(b.start) FROM Booking b + WHERE b.item.id = i.id AND b.status = 'APPROVED' AND b.start > CURRENT_TIMESTAMP) + ) + FROM Item i + JOIN User u ON i.owner.id = u.id + WHERE i.id = :itemId""") + Optional findItemWithBookings(@Param("itemId") Long itemId); + + @Query(""" + SELECT new ru.practicum.shareit.item.dto.ItemCommentDto( + i.id, + i.name, + i.description, + i.available, + new ru.practicum.shareit.user.dto.UserDto(u.id, u.name, u.email), + (SELECT MAX(b.start) FROM Booking b + WHERE b.item.id = i.id AND b.status = 'APPROVED' AND b.end < CURRENT_TIMESTAMP), + (SELECT MIN(b.start) FROM Booking b + WHERE b.item.id = i.id AND b.status = 'APPROVED' AND b.start > CURRENT_TIMESTAMP) + ) + FROM Item i + JOIN User u ON i.owner.id = u.id + WHERE i.owner.id = :ownerId + ORDER BY i.id ASC""") + List findAllByOwnerWithBookings(@Param("ownerId") Long ownerId); + + @Query("SELECT i FROM Item i " + + "WHERE i.available = true " + + "AND (LOWER(i.name) LIKE LOWER(CONCAT('%', :text, '%')) " + + "OR LOWER(i.description) LIKE LOWER(CONCAT('%', :text, '%')))") + List getItemsByNameOrDescription(@Param("text") String text); } diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java b/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java deleted file mode 100644 index 978e29b..0000000 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepositoryImpl.java +++ /dev/null @@ -1,84 +0,0 @@ -package ru.practicum.shareit.item.repository; - -import org.springframework.stereotype.Component; -import ru.practicum.shareit.exception.NotFoundException; -import ru.practicum.shareit.item.model.Item; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Component -public class ItemRepositoryImpl implements ItemRepository { - private long id = 1L; - private final Map itemMap = new HashMap<>(); - - @Override - public long getNextId() { - return id++; - } - - @Override - public Item create(Item item) { - long id = getNextId(); - item.setId(id); - itemMap.put(id, item); - return item; - } - - @Override - public Item update(Item item) { - long id = item.getId(); - checkItemId(id); - checkOwnerId(item); - Item itemSaved = itemMap.get(id); - if (item.getName() != null) { - itemSaved.setName(item.getName()); - } - if (item.getDescription() != null) { - itemSaved.setDescription(item.getDescription()); - } - if (item.getAvailable() != null) { - itemSaved.setAvailable(item.getAvailable()); - } - itemMap.put(id, itemSaved); - return itemSaved; - } - - @Override - public Item getItemById(long itemId) { - checkItemId(itemId); - return itemMap.get(itemId); - } - - @Override - public List getItemByOwnerId(long ownerId) { - return itemMap.values() - .stream() - .filter(item -> item.getOwner() == ownerId) - .toList(); - } - - @Override - public List getItemsByNameOrDescription(String text) { - return itemMap.values().stream() - .filter(item -> item.getAvailable().equals(true)) - .filter(item -> item.getName().toLowerCase().contains(text) || - item.getDescription().toLowerCase().contains(text)) - .toList(); - } - - private void checkItemId(long id) { - if (!itemMap.containsKey(id)) { - throw new NotFoundException("item под номером: id = " + id + " отсутствует"); - } - } - - private void checkOwnerId(Item item) { - Item itemSaved = itemMap.get(item.getId()); - if (item.getOwner() != itemSaved.getOwner()) { - throw new NotFoundException("Редактировать item может только владелец"); - } - } - -} diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/src/main/java/ru/practicum/shareit/item/service/ItemService.java index cfc6682..333ccf9 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -1,17 +1,20 @@ package ru.practicum.shareit.item.service; -import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.dto.*; import java.util.List; public interface ItemService { - Item create(Item item); + CommentDtoReturn createComment(long authorId, long itemId, CommentCreateDto createDto); - Item update(Item item); + ItemCommentDto getByItemIdWithComment(long userId, long itemId); - Item getItemById(long itemId); + List findAllByOwnerIdWithBookings(Long ownerId); - List getItemByOwnerId(long ownerId); + ItemDto createItem(ItemCreateDto createDto, long ownerId); - List getItemsByNameOrDescription(String text); + ItemDto update(ItemUpdateDto updateDto, long ownerId, long itemId); + + + List getItemsByNameOrDescription(String text); } diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java index 386fdf6..6638ba9 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -2,54 +2,132 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.BookingRepository; +import ru.practicum.shareit.exception.BadRequestException; +import ru.practicum.shareit.exception.ConflictException; import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.CommentMapper; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.dto.*; +import ru.practicum.shareit.item.model.Comment; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.CommentRepository; import ru.practicum.shareit.item.repository.ItemRepository; -import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.UserRepository; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class ItemServiceImpl implements ItemService { private final ItemRepository itemRepository; private final UserRepository userRepository; + private final BookingRepository bookingRepository; + private final ItemMapper itemMapper; + private final CommentMapper commentMapper; + private final CommentRepository commentRepository; @Override - public Item create(Item item) { - checkUser(item); - return itemRepository.create(item); + public CommentDtoReturn createComment(long authorId, long itemId, CommentCreateDto createDto) { + if (!bookingRepository.hasUserBookedItem(authorId, itemId)) { + throw new BadRequestException("Добавлять комментарий может только тот, кто брал вещь в аренду"); + } + User author = checkAndReturnUser(authorId); + Item item = checkAndReturnItem(itemId); + Comment comment = commentMapper.commentCreateDtoToModel(createDto); + comment.setAuthor(author); + comment.setItem(item); + comment = commentRepository.save(comment); + return commentMapper.modelToReturnDto(comment); + } + + @Override + public ItemCommentDto getByItemIdWithComment(long userId, long itemId) { + ItemCommentDto itemCommentDto = itemRepository.findItemWithBookings(itemId) + .orElseThrow(() -> new NotFoundException("Вещь не найдена")); + if (itemCommentDto.getOwner().getId() != userId) { + itemCommentDto.setLastBooking(null); + itemCommentDto.setNextBooking(null); + } + List commentDto = commentRepository.findByItemId(itemId); + itemCommentDto.setComments(commentDto); + return itemCommentDto; } @Override - public Item update(Item item) { - checkUser(item); - return itemRepository.update(item); + public List findAllByOwnerIdWithBookings(Long ownerId) { + List items = itemRepository.findAllByOwnerWithBookings(ownerId); + + if (items.isEmpty()) { + throw new NotFoundException("У пользователя с ID " + ownerId + " нет вещей"); + } + + List itemIds = items.stream() + .map(ItemCommentDto::getId) + .collect(Collectors.toList()); + + Map> commentsMap = commentRepository.findByItemIds(itemIds) + .stream() + .collect(Collectors.groupingBy( + comment -> comment.getItem().getId(), + Collectors.toList() + )); + + items.forEach(item -> + item.setComments(commentsMap.getOrDefault(item.getId(), Collections.emptyList())) + ); + return items; } @Override - public Item getItemById(long itemId) { - return itemRepository.getItemById(itemId); + public ItemDto createItem(ItemCreateDto createDto, long ownerId) { + Item item = itemMapper.createDtoToModel(createDto); + User user = checkAndReturnUser(ownerId); + item.setOwner(user); + return itemMapper.modelToItemDto(itemRepository.save(item)); } @Override - public List getItemByOwnerId(long ownerId) { - return itemRepository.getItemByOwnerId(ownerId); + public ItemDto update(ItemUpdateDto updateDto, long ownerId, long itemId) { + User owner = checkAndReturnUser(ownerId); + Item item = checkAndReturnItem(itemId); + + if (item.getOwner().getId() != ownerId) { + throw new ConflictException("Редактировать Item может только владелец"); + } + if (updateDto.getName() != null) { + item.setName(updateDto.getName()); + } + if (updateDto.getDescription() != null) { + item.setDescription(updateDto.getDescription()); + } + if (updateDto.getAvailable() != null) { + item.setAvailable(updateDto.getAvailable()); + } + item.setOwner(owner); + itemRepository.save(item); + return itemMapper.modelToItemDto(item); } @Override - public List getItemsByNameOrDescription(String text) { + public List getItemsByNameOrDescription(String text) { if (text == null || text.isEmpty()) { return List.of(); } - return itemRepository.getItemsByNameOrDescription(text.toLowerCase()); + List items = itemRepository.getItemsByNameOrDescription(text.toLowerCase()); + return itemMapper.listModelToDto(items); } - private void checkUser(Item item) { - if (userRepository.getUserById(item.getOwner()) == null) { - System.out.println(item); - throw new NotFoundException("User отсутствует"); - } + private Item checkAndReturnItem(long itemId) { + return itemRepository.findById(itemId).orElseThrow(() -> new NotFoundException("Item не найден")); + } + + private User checkAndReturnUser(long id) { + return userRepository.findById(id).orElseThrow(() -> new NotFoundException("User не найден")); } } diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java index 1ae6480..1bd5ea1 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -1,14 +1,33 @@ package ru.practicum.shareit.request; -import lombok.Data; +import jakarta.persistence.*; +import lombok.*; import java.time.LocalDateTime; -@Data + +@Builder +@Getter +@Setter +@ToString +@Entity +@Table(name = "requests") public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String description; + + @Column(name = "requestor", nullable = false) private Long requestor; + + @Column(name = "time_request", nullable = false) private LocalDateTime dateTimeRequest; + + @Override + public int hashCode() { + return getClass().hashCode(); + } } diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/src/main/java/ru/practicum/shareit/user/User.java index c3373a9..af0421a 100644 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ b/src/main/java/ru/practicum/shareit/user/User.java @@ -1,13 +1,27 @@ package ru.practicum.shareit.user; -import lombok.Builder; -import lombok.Data; +import jakarta.persistence.*; +import lombok.*; +@NoArgsConstructor +@AllArgsConstructor @Builder -@Data +@Getter +@Setter +@ToString +@Entity +@Table(name = "users") public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + + @Column(nullable = false) private String name; + + @Column(nullable = false) private String email; + + } diff --git a/src/main/java/ru/practicum/shareit/user/UserRepository.java b/src/main/java/ru/practicum/shareit/user/UserRepository.java new file mode 100644 index 0000000..77bcf80 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/UserRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.user; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java index ac7278e..babd3d4 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -1,8 +1,12 @@ package ru.practicum.shareit.user.dto; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; +@AllArgsConstructor +@NoArgsConstructor @Data @Builder public class UserDto { diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java deleted file mode 100644 index 87f8453..0000000 --- a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.practicum.shareit.user.repository; - -import ru.practicum.shareit.user.User; - -import java.util.Collection; - -public interface UserRepository { - User create(User user); - - User update(User user); - - User getUserById(long id); - - Collection getAll(); - - void deleteUser(long userId); -} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java deleted file mode 100644 index dce0733..0000000 --- a/src/main/java/ru/practicum/shareit/user/repository/UserRepositoryImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -package ru.practicum.shareit.user.repository; - -import org.springframework.stereotype.Component; -import ru.practicum.shareit.exception.ConflictException; -import ru.practicum.shareit.exception.NotFoundException; -import ru.practicum.shareit.user.User; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Component -public class UserRepositoryImpl implements UserRepository { - private long id = 1L; - private final Map userMap = new HashMap<>(); - - public long getNextId() { - return id++; - } - - @Override - public User create(User user) { - checkCloneEmail(user.getEmail()); - long id = getNextId(); - user.setId(id); - userMap.put(id, user); - return user; - } - - @Override - public User update(User user) { - long id = user.getId(); - checkUserId(id); - User userSaved = userMap.get(id); - - if (user.getName() != null) { - userSaved.setName(user.getName()); - } - if (user.getEmail() != null) { - String email = user.getEmail(); - checkCloneEmail(email); - userSaved.setEmail(email); - } - return userSaved; - } - - @Override - public User getUserById(long id) { - checkUserId(id); - return userMap.get(id); - } - - @Override - public Collection getAll() { - return userMap.values(); - } - - @Override - public void deleteUser(long userId) { - userMap.remove(userId); - } - - private void checkCloneEmail(String email) { - List users = userMap.values().stream() - .filter(user -> user.getEmail().equals(email)) - .toList(); - if (!users.isEmpty()) { - throw new ConflictException("email уже существует"); - } - } - - private void checkUserId(long id) { - if (!userMap.containsKey(id)) { - throw new NotFoundException("User отсутствует"); - } - } - -} diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java index 2a99e6a..257e7f2 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -2,8 +2,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; import ru.practicum.shareit.user.User; -import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.user.UserRepository; import java.util.Collection; @@ -14,26 +15,39 @@ public class UserServiceImpl implements UserService { @Override public User create(User user) { - return userRepository.create(user); + return userRepository.save(user); } @Override - public User update(User user) { - return userRepository.update(user); + public User update(User newUser) { + User user = checkAndReturnUser(newUser.getId()); + + if (newUser.getName() != null) { + user.setName(newUser.getName()); + } + if (newUser.getEmail() != null) { + user.setEmail(newUser.getEmail()); + } + + return userRepository.save(user); } @Override public User getUserById(long id) { - return userRepository.getUserById(id); + return checkAndReturnUser(id); } @Override public Collection getAll() { - return userRepository.getAll(); + return userRepository.findAll(); } @Override public void deleteUser(long userId) { - userRepository.deleteUser(userId); + userRepository.deleteById(userId); + } + + private User checkAndReturnUser(long id) { + return userRepository.findById(id).orElseThrow(() -> new NotFoundException("User не найден")); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1a133c3..2201553 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,6 @@ spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.format_sql=true + spring.sql.init.mode=always logging.level.org.zalando.logbook= TRACE @@ -9,8 +10,16 @@ logging.level.org.springframework.transaction=INFO logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG -# TODO Append connection to DB -#spring.datasource.driverClassName -#spring.datasource.url -#spring.datasource.username -#spring.datasource.password + +spring.datasource.driverClassName=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:5432/shareit +spring.datasource.username=shareit +spring.datasource.password=12345 + + + +#spring.datasource.driverClassName=org.h2.Driver +#spring.datasource.url=jdbc:h2:mem:shareit +#spring.datasource.driverClassName=org.h2.Driver +#spring.datasource.username=shareit +#spring.datasource.password=shareit \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..9e5a425 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,48 @@ + + +CREATE TABLE IF NOT EXISTS users ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS requests ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + description VARCHAR(255), + requestor INTEGER NOT NULL, + time_request TIMESTAMP WITHOUT TIME ZONE, + FOREIGN KEY (requestor) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS items ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description VARCHAR(255), + available boolean NOT NULL, + owner_id INTEGER NOT NULL, + FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS booking ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + start_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + end_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + item_id INTEGER NOT NULL, + booker_id INTEGER NOT NULL, + status VARCHAR(20) NOT NULL, + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, + FOREIGN KEY (booker_id) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT valid_status CHECK ( + status IN ('WAITING', 'APPROVED', 'REJECTED', 'CANCELED') + ) +); + +CREATE TABLE IF NOT EXISTS comments ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + text VARCHAR(200) NOT NULL, + item_id INTEGER NOT NULL, + author_id INTEGER NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, + FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..8ae339c --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,5 @@ +spring.config.activate.on-profile=test +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:shareit +spring.datasource.username=shareit +spring.datasource.password=shareit \ No newline at end of file