diff --git a/pom.xml b/pom.xml index 2db888c..1b18148 100644 --- a/pom.xml +++ b/pom.xml @@ -51,15 +51,28 @@ h2 test + org.springframework.boot spring-boot-starter-test test + org.springframework.boot spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.postgresql + postgresql + runtime + diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/src/main/java/ru/practicum/shareit/ShareItApp.java index a00ad56..83b60e0 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/src/main/java/ru/practicum/shareit/ShareItApp.java @@ -5,9 +5,7 @@ @SpringBootApplication public class ShareItApp { - public static void main(String[] args) { SpringApplication.run(ShareItApp.class, args); } - } diff --git a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java index 427a4b6..3498754 100644 --- a/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/controller/BookingController.java @@ -1,12 +1,63 @@ package ru.practicum.shareit.booking.controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +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.model.BookingState; +import ru.practicum.shareit.booking.service.BookingService; + +import java.util.Collection; /** * TODO Sprint add-bookings. */ @RestController @RequestMapping(path = "/bookings") +@RequiredArgsConstructor public class BookingController { + + private final BookingService bookingService; + + @PostMapping + public Booking createBooking(@RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @Valid @RequestBody BookingDto bookingDto) { + + return bookingService.createBooking(userId, bookingDto); + } + + @PatchMapping("/{bookingId}") + public Booking updateBookingStatus( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long bookingId, + @RequestParam Boolean approved) { + + return bookingService.updateBookingStatus(userId, bookingId, approved); + } + + @GetMapping("/{bookingId}") + public Booking getBooking( + @RequestHeader("X-Sharer-User-Id") @Positive Long userId, + @PathVariable @Positive Long bookingId) { + + return bookingService.getBooking(userId, bookingId); + } + + @GetMapping + public Collection findByBookerAndState( + @RequestHeader("X-Sharer-User-Id") @Positive Long bookerId, + @RequestParam (defaultValue = "ALL") BookingState state) { + + return bookingService.findByBookerAndState(bookerId, state); + } + + @GetMapping("/owner") + public Collection findByOwnerAndState( + @RequestHeader("X-Sharer-User-Id") @Positive Long ownerId, + @RequestParam (defaultValue = "ALL") BookingState state) { + + return bookingService.findByOwnerAndState(ownerId, state); + } } diff --git a/src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java index 80e814a..b328a53 100644 --- a/src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java +++ b/src/main/java/ru/practicum/shareit/booking/dao/BookingMapper.java @@ -6,13 +6,10 @@ @UtilityClass public class BookingMapper { - public static BookingDto mapToBooking(Booking booking) { - return new BookingDto( - booking.getStart(), - booking.getEnd(), - booking.getItem(), - booking.getBooker(), - booking.getStatus() - ); + public static Booking mapToBooking(BookingDto bookingDto) { + Booking booking = new Booking(); + booking.setStart(bookingDto.getStart()); + booking.setEnd(bookingDto.getEnd()); + return booking; } } diff --git a/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java new file mode 100644 index 0000000..1e08da0 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/dao/BookingRepository.java @@ -0,0 +1,52 @@ +package ru.practicum.shareit.booking.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.model.LastAndNextDate; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@Repository +public interface BookingRepository extends JpaRepository { + + Collection findByBookerIdOrderByEndDesc(Long bookerId); + + Collection findByItemOwnerIdOrderByEndDesc(Long ownerId); + + Collection findByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(Long bookerId, LocalDateTime now, + LocalDateTime now1); + + Collection findByBookerIdAndStatusIsOrderByEndDesc(Long bookerId, BookingStatus bookingStatus); + + Collection findByBookerIdAndEndIsBeforeOrderByEndDesc(Long bookerId, LocalDateTime now); + + Collection findByBookerIdAndStartIsAfterOrderByEndDesc(Long bookerId, LocalDateTime now); + + Collection findByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(Long ownerId, LocalDateTime now, + LocalDateTime now1); + + Collection findByItemOwnerIdAndStatusIsOrderByEndDesc(Long ownerId, BookingStatus bookingStatus); + + Collection findByItemOwnerIdAndEndIsBeforeOrderByEndDesc(Long ownerId, LocalDateTime now); + + Collection findByItemOwnerIdAndStartIsAfterOrderByEndDesc(Long ownerId, LocalDateTime now); + + boolean existsByItemIdAndBookerIdAndStatusIsAndEndBefore(Long itemId, Long userId, BookingStatus bookingStatus, + LocalDateTime now); + + @Query(""" + SELECT + b.item.id as itemId, + MAX(CASE WHEN b.start < CURRENT_TIMESTAMP THEN b.start ELSE NULL END) as lastBooking, + MIN(CASE WHEN b.start > CURRENT_TIMESTAMP THEN b.start ELSE NULL END) as nextBooking + FROM Booking b + WHERE b.item.ownerId = ?1 + GROUP BY b.item.id + """) + List findLastAndNextDatesByOwnerId(long ownerId); +} 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 2f99f25..040c09d 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,20 +1,29 @@ package ru.practicum.shareit.booking.dto; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; -import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.booking.model.BookingStatus; -import ru.practicum.shareit.user.model.User; +import lombok.Getter; +import lombok.Setter; -import java.time.LocalDate; +import java.time.LocalDateTime; /** * TODO Sprint add-bookings. */ +@Setter +@Getter @AllArgsConstructor public class BookingDto { - private LocalDate start; - private LocalDate end; - private Item item; - private User booker; - private BookingStatus status; + @NotNull + @Positive + private Long itemId; + @NotNull + @FutureOrPresent + private LocalDateTime start; + @NotNull + @FutureOrPresent + private LocalDateTime end; + } diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java index 090f6c9..33ea486 100644 --- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -1,22 +1,45 @@ package ru.practicum.shareit.booking.model; +import jakarta.persistence.*; import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.user.model.User; -import java.time.LocalDate; +import java.time.LocalDateTime; /** * TODO Sprint add-bookings. */ -@Data +@Entity @AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +@Table(name = "bookings") public class Booking { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private LocalDate start; - private LocalDate end; + + @Column(name = "start_date") + private LocalDateTime start; + + @Column(name = "end_date") + private LocalDateTime end; + + @ManyToOne + @JoinColumn(name = "item_id") private Item item; + + @ManyToOne + @JoinColumn(name = "booker_id") private User booker; + + @Column(name = "status") + @Enumerated(EnumType.STRING) private BookingStatus status; } diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingState.java b/src/main/java/ru/practicum/shareit/booking/model/BookingState.java new file mode 100644 index 0000000..a574273 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/model/BookingState.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.booking.model; + +public enum BookingState { + ALL, + CURRENT, + PAST, + FUTURE, + WAITING, + REJECTED +} diff --git a/src/main/java/ru/practicum/shareit/booking/model/LastAndNextDate.java b/src/main/java/ru/practicum/shareit/booking/model/LastAndNextDate.java new file mode 100644 index 0000000..c793bb1 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/model/LastAndNextDate.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.booking.model; + +import java.time.LocalDateTime; + +public interface LastAndNextDate { + Long getItemId(); + + LocalDateTime getLastBooking(); + + LocalDateTime getNextBooking(); +} 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..4e08db2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingService.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.booking.service; + +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingState; + +import java.util.Collection; + +public interface BookingService { + Booking createBooking(Long bookerId, BookingDto bookingDto); + + Booking updateBookingStatus(Long userId, Long bookingId, Boolean approved); + + Booking getBooking(Long userId, Long bookingId); + + Collection findByBookerAndState(Long bookerId, BookingState state); + + Collection findByOwnerAndState(Long ownerId, BookingState 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..87396bf --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -0,0 +1,125 @@ +package ru.practicum.shareit.booking.service; + +import jakarta.validation.ValidationException; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.dao.BookingRepository; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingState; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.exceptions.BadRequestException; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.Collection; + +import static ru.practicum.shareit.booking.dao.BookingMapper.mapToBooking; + +@Service +@AllArgsConstructor +public class BookingServiceImpl implements BookingService { + + private final BookingRepository bookingRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + + @Override + @Transactional + public Booking createBooking(Long bookerId, BookingDto bookingDto) { + if (!bookingDto.getStart().isBefore(bookingDto.getEnd())) { + throw new BadRequestException("Начало не может быть после конца бронирования"); + } + User user = userRepository.findById(bookerId) + .orElseThrow(() -> new NotFoundException("Пользователь с id " + bookerId + " не найден")); + Item item = itemRepository.findById(bookingDto.getItemId()) + .orElseThrow(() -> new NotFoundException("Предмет с id " + bookingDto.getItemId() + " не найден")); + if (!item.getIsAvailable()) { + throw new BadRequestException("Предмет уже занят"); + } + + Booking booking = mapToBooking(bookingDto); + booking.setItem(item); + booking.setBooker(user); + booking.setStatus(BookingStatus.WAITING); + return bookingRepository.save(booking); + } + + @Override + @Transactional + public Booking updateBookingStatus(Long userId, Long bookingId, Boolean approved) { + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Бронирование не найдено")); + if (!booking.getItem().getOwnerId().equals(userId)) { + throw new BadRequestException("Менять статус может только владелец"); + } + if (approved) { + booking.setStatus(BookingStatus.APPROVED); + } else { + booking.setStatus(BookingStatus.REJECTED); + } + return bookingRepository.save(booking); + } + + @Override + public Booking getBooking(Long userId, Long bookingId) { + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Бронирование не найдено")); + if (!booking.getBooker().getId().equals(userId) && !booking.getItem().getOwnerId().equals(userId)) { + throw new ValidationException("Только владелец или арендатор могут посмотреть бронирование"); + } + return booking; + } + + @Override + @Transactional(readOnly = true) + public Collection findByBookerAndState(Long bookerId, BookingState state) { + if (userRepository.findById(bookerId).isEmpty()) { + throw new NotFoundException("Пользователь с id " + bookerId + " не найден"); + } + LocalDateTime now = LocalDateTime.now(); + + switch (state) { + case CURRENT: + return bookingRepository.findByBookerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(bookerId, now, now); + case REJECTED: + return bookingRepository.findByBookerIdAndStatusIsOrderByEndDesc(bookerId, BookingStatus.REJECTED); + case WAITING: + return bookingRepository.findByBookerIdAndStatusIsOrderByEndDesc(bookerId, BookingStatus.WAITING); + case PAST: + return bookingRepository.findByBookerIdAndEndIsBeforeOrderByEndDesc(bookerId, now); + case FUTURE: + return bookingRepository.findByBookerIdAndStartIsAfterOrderByEndDesc(bookerId, now); + } + return bookingRepository.findByBookerIdOrderByEndDesc(bookerId); + } + + @Override + @Transactional(readOnly = true) + public Collection findByOwnerAndState(Long ownerId, BookingState state) { + if (userRepository.findById(ownerId).isEmpty()) { + throw new NotFoundException("Пользователь с id " + ownerId + " не найден"); + } + LocalDateTime now = LocalDateTime.now(); + + switch (state) { + case CURRENT: + return bookingRepository.findByItemOwnerIdAndStartIsBeforeAndEndIsBeforeOrderByEndDesc(ownerId, now, + now); + case REJECTED: + return bookingRepository.findByItemOwnerIdAndStatusIsOrderByEndDesc(ownerId, BookingStatus.REJECTED); + case WAITING: + return bookingRepository.findByItemOwnerIdAndStatusIsOrderByEndDesc(ownerId, BookingStatus.WAITING); + case PAST: + return bookingRepository.findByItemOwnerIdAndEndIsBeforeOrderByEndDesc(ownerId, now); + case FUTURE: + return bookingRepository.findByItemOwnerIdAndStartIsAfterOrderByEndDesc(ownerId, now); + } + return bookingRepository.findByItemOwnerIdOrderByEndDesc(ownerId); + } +} diff --git a/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java index ef558d3..871c064 100644 --- a/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java +++ b/src/main/java/ru/practicum/shareit/exceptions/ErrorHandler.java @@ -26,9 +26,16 @@ public ErrorResponse handleNotFoundException(final NotFoundException e) { } @ExceptionHandler - @ResponseStatus(HttpStatus.NOT_FOUND) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleInternalServerException(final InternalServerException e) { log.warn("Ошибка сервера"); return new ErrorResponse(e.getMessage()); } -} + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleThrowable(final Throwable e) { + log.error("Непредвиденная ошибка"); + return new ErrorResponse(e.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java index b6fdcf2..5ddfb15 100644 --- a/src/main/java/ru/practicum/shareit/item/controller/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/controller/ItemController.java @@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.model.Comment; import ru.practicum.shareit.validation.OnCreate; import ru.practicum.shareit.validation.OnUpdate; import ru.practicum.shareit.item.dto.ItemDto; @@ -33,9 +35,8 @@ public ItemDto addItem(@Validated(OnCreate.class) @RequestBody final ItemDto ite public ItemDto updateItem(@PathVariable("itemId") final long itemId, @RequestHeader("X-Sharer-User-Id") long ownerId, @Validated(OnUpdate.class) @RequestBody final ItemDto itemDto) { - itemDto.setId(itemId); log.info("Предмет обновлен"); - return itemService.updateItem(itemDto, ownerId); + return itemService.updateItem(itemDto, itemId, ownerId); } @GetMapping("/{itemId}") @@ -55,4 +56,12 @@ public Collection getNecessaryItem(@RequestParam String text) { log.info("Список предметов содержащих " + text + " выведен"); return itemService.getNecessaryItem(text); } + + @PostMapping("/{itemId}/comment") + public CommentDto addComment(@PathVariable("itemId") final long itemId, + @RequestHeader("X-Sharer-User-Id") long ownerId, + @RequestBody Comment comment) { + log.info("Комментарий добавлен"); + return itemService.addComment(ownerId, itemId, comment); + } } diff --git a/src/main/java/ru/practicum/shareit/item/dao/CommentMapper.java b/src/main/java/ru/practicum/shareit/item/dao/CommentMapper.java new file mode 100644 index 0000000..f12ff09 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dao/CommentMapper.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.item.dao; + +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.model.Comment; + +public class CommentMapper { + public static CommentDto toCommentDto(Comment comment) { + CommentDto commentDto = new CommentDto(); + commentDto.setId(comment.getId()); + commentDto.setItemId(comment.getItem().getId()); + commentDto.setAuthorName(comment.getAuthor().getName()); + commentDto.setText(comment.getText()); + commentDto.setCreated(comment.getCreated()); + + return commentDto; + } +} diff --git a/src/main/java/ru/practicum/shareit/item/dao/CommentRepository.java b/src/main/java/ru/practicum/shareit/item/dao/CommentRepository.java new file mode 100644 index 0000000..3a6202c --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dao/CommentRepository.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.item.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.model.Comment; + +import java.util.Collection; +import java.util.List; + +@Repository +public interface CommentRepository extends JpaRepository { + Collection findByItemId(Long itemId); + + @Query("SELECT c FROM Comment c JOIN FETCH c.item WHERE c.item.ownerId = ?1") + List findByItemOwnerId(Long ownerId); +} diff --git a/src/main/java/ru/practicum/shareit/item/dao/InMemoryItemStorage.java b/src/main/java/ru/practicum/shareit/item/dao/InMemoryItemStorage.java deleted file mode 100644 index 4cfb451..0000000 --- a/src/main/java/ru/practicum/shareit/item/dao/InMemoryItemStorage.java +++ /dev/null @@ -1,79 +0,0 @@ -package ru.practicum.shareit.item.dao; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Repository; -import ru.practicum.shareit.exceptions.NotFoundException; -import ru.practicum.shareit.item.model.Item; - -import java.util.*; - - -@Slf4j -@Repository -@RequiredArgsConstructor -public class InMemoryItemStorage { - private final Map items = new HashMap<>(); - - private Long getNextId() { - long currentMaxId = items.keySet() - .stream() - .mapToLong(id -> id) - .max() - .orElse(0); - return ++currentMaxId; - } - - public Item addItem(Item item) { - item.setId(getNextId()); - items.put(item.getId(), item); - return item; - } - - public Item updateItem(Item newItem) { - Item item = items.get(newItem.getId()); - if (newItem.getName() != null) { - item.setName(newItem.getName()); - } - if (newItem.getDescription() != null) { - item.setDescription(newItem.getDescription()); - } - if (newItem.getAvailable() != null) { - item.setAvailable(newItem.getAvailable()); - } - return item; - } - - public Item getItem(long itemId) { - if (!items.containsKey(itemId)) { - log.warn("Предмет с указанным id " + itemId + " не найден"); - throw new NotFoundException("Предмет с id = " + itemId + " не найден"); - } - return items.get(itemId); - } - - public List getAllOwnerItems(long ownerId) { - List ownerItems = new ArrayList<>(); - for (Item item : items.values()) { - if (item.getOwnerId() == ownerId) { - ownerItems.add(item); - } - } - return ownerItems; - } - - public List getNecessaryItem(String text) { - List necessaryItems = new ArrayList<>(); - if (text == null || text.isBlank()) { - return Collections.emptyList(); - } - text = text.toLowerCase(Locale.ROOT); - for (Item item : items.values()) { - if (item.getAvailable() && item.getName().toLowerCase(Locale.ROOT).contains(text) - || item.getDescription().toLowerCase(Locale.ROOT).contains(text)) { - necessaryItems.add(item); - } - } - return necessaryItems; - } -} diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java index 689a5aa..5829d97 100644 --- a/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/dao/ItemMapper.java @@ -7,12 +7,12 @@ @UtilityClass public class ItemMapper { public static ItemDto toItemDto(Item item) { - return new ItemDto( - item.getId(), - item.getName(), - item.getDescription(), - item.getAvailable() - ); + ItemDto itemDto = new ItemDto(); + itemDto.setId(item.getId()); + itemDto.setName(item.getName()); + itemDto.setDescription(item.getDescription()); + itemDto.setAvailable(item.getIsAvailable()); + return itemDto; } public static Item toItem(ItemDto itemDto) { @@ -20,7 +20,7 @@ public static Item toItem(ItemDto itemDto) { item.setId(itemDto.getId()); item.setName(itemDto.getName()); item.setDescription(itemDto.getDescription()); - item.setAvailable(itemDto.getAvailable()); + item.setIsAvailable(itemDto.getAvailable()); return item; } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java new file mode 100644 index 0000000..f2bc1cf --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dao/ItemRepository.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.item.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.model.Item; + +import java.util.Collection; +import java.util.List; + +@Repository +public interface ItemRepository extends JpaRepository { + + @Query(""" + SELECT i + FROM Item i + WHERE i.isAvailable = true + AND (LOWER(i.name) LIKE LOWER(%:text%) + OR LOWER(i.description) LIKE LOWER(%:text%)) + """) + Collection searchByNameOrDescription(String text); + + List findByOwnerId(Long userId); +} 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..1a228f2 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.item.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CommentDto { + private Long id; + private Long itemId; + private String authorName; + private String text; + private LocalDateTime created; +} 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 4c141ef..f8e2967 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -4,13 +4,18 @@ import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import ru.practicum.shareit.validation.OnCreate; +import java.time.LocalDateTime; +import java.util.Collection; + /** * TODO Sprint add-controllers. */ @Data @AllArgsConstructor +@NoArgsConstructor public class ItemDto { private Long id; @NotBlank(groups = OnCreate.class, message = "Название предмета должно быть заполнено") @@ -19,4 +24,7 @@ public class ItemDto { private String description; @NotNull(groups = OnCreate.class, message = "Статус должен быть заполнен") private Boolean available; + private LocalDateTime lastBooking; + private LocalDateTime nextBooking; + private Collection comments; } 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..8463d6a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/model/Comment.java @@ -0,0 +1,35 @@ +package ru.practicum.shareit.item.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.user.model.User; +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "comments") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne + @JoinColumn(name = "author_id") + private User author; + + @Column(name = "content") + private String text; + + @Column(name = "created_at") + 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 68193d0..3dab078 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,19 +1,34 @@ package ru.practicum.shareit.item.model; +import jakarta.persistence.*; import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; /** * TODO Sprint add-controllers. */ -@Data +@Entity @AllArgsConstructor @NoArgsConstructor +@Getter +@Setter +@Table(name = "items") public class Item { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "name") private String name; + + @Column(name = "description") private String description; - private Boolean available; + + @Column(name = "available") + private Boolean isAvailable; + + @Column(name = "owner_id") private Long ownerId; } 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 5119e06..277a7e9 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -1,6 +1,8 @@ package ru.practicum.shareit.item.service; +import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.model.Comment; import java.util.Collection; import java.util.List; @@ -9,11 +11,13 @@ public interface ItemService { ItemDto addItem(ItemDto itemDto, Long userId); - ItemDto updateItem(ItemDto itemDto, long userId); + ItemDto updateItem(ItemDto itemDto, Long itemId, long userId); ItemDto getItem(long itemId); List getAllOwnerItems(long ownerId); Collection getNecessaryItem(String text); + + CommentDto addComment(Long userId, Long itemId, Comment comment); } 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 762aaee..e25ce6e 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -3,15 +3,27 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.dao.BookingRepository; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.model.LastAndNextDate; +import ru.practicum.shareit.exceptions.BadRequestException; import ru.practicum.shareit.exceptions.NotFoundException; -import ru.practicum.shareit.item.dao.InMemoryItemStorage; +import ru.practicum.shareit.item.dao.CommentMapper; +import ru.practicum.shareit.item.dao.CommentRepository; +import ru.practicum.shareit.item.dao.ItemRepository; +import ru.practicum.shareit.item.dto.CommentDto; 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.dao.InMemoryUserStorage; +import ru.practicum.shareit.user.dao.UserRepository; +import ru.practicum.shareit.user.model.User; -import java.util.ArrayList; -import java.util.List; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; +import static ru.practicum.shareit.item.dao.CommentMapper.toCommentDto; import static ru.practicum.shareit.item.dao.ItemMapper.toItem; import static ru.practicum.shareit.item.dao.ItemMapper.toItemDto; @@ -19,52 +31,137 @@ @Service @RequiredArgsConstructor public class ItemServiceImpl implements ItemService { - private final InMemoryItemStorage inMemoryItemStorage; - private final InMemoryUserStorage inMemoryUserStorage; + private final ItemRepository itemRepository; + private final UserRepository userRepository; + private final CommentRepository commentRepository; + private final BookingRepository bookingRepository; @Override + @Transactional public ItemDto addItem(ItemDto itemDto, Long ownerId) { - if (!inMemoryUserStorage.getUsers().containsKey(ownerId)) { + if (!userRepository.existsById(ownerId)) { log.warn("Владелец с указанным id " + ownerId + " не найден"); throw new NotFoundException("Владелец с id " + ownerId + " не найден"); } Item item = toItem(itemDto); item.setOwnerId(ownerId); - return toItemDto(inMemoryItemStorage.addItem(item)); + return toItemDto(itemRepository.save(item)); } @Override - public ItemDto updateItem(ItemDto itemDto, long userId) { - if (!inMemoryUserStorage.getUsers().containsKey(userId)) { + @Transactional + public ItemDto updateItem(ItemDto itemDto,Long itemId, long userId) { + if (!userRepository.existsById(userId)) { log.warn("Пользователь с указанным id " + userId + " не найден"); throw new NotFoundException("Пользователь с id " + userId + " не найден"); } - Item newItem = toItem(itemDto); + Item newItem = itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException("Предмет с id " + itemId + " не найден")); newItem.setOwnerId(userId); - return toItemDto(inMemoryItemStorage.updateItem(newItem)); + + if (itemDto.getName() != null) { + newItem.setName(itemDto.getName()); + } + if (itemDto.getDescription() != null) { + newItem.setDescription(itemDto.getDescription()); + } + if (itemDto.getAvailable() != null) { + newItem.setIsAvailable(itemDto.getAvailable()); + } + return toItemDto(itemRepository.save(newItem)); } @Override + @Transactional(readOnly = true) public ItemDto getItem(long itemId) { - return toItemDto(inMemoryItemStorage.getItem(itemId)); + Item item = itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException("Предмет с id " + itemId + " не найден")); + + ItemDto itemDto = toItemDto(item); + Collection comments = commentRepository.findByItemId(itemId); + + return addCommentsToItem(itemDto, comments); } @Override + @Transactional(readOnly = true) public List getAllOwnerItems(long ownerId) { - List itemDtoList = new ArrayList<>(); - for (Item item : inMemoryItemStorage.getAllOwnerItems(ownerId)) { - itemDtoList.add(toItemDto(item)); + List items = itemRepository.findByOwnerId(ownerId); + + if (items.isEmpty()) { + return Collections.emptyList(); } - return itemDtoList; + + List itemIds = items.stream() + .map(Item::getId) + .toList(); + + List comments = commentRepository.findByItemOwnerId(ownerId); + Map> commentsMap = comments.stream() + .collect(Collectors.groupingBy( + comment -> comment.getItem().getId(), + Collectors.mapping(CommentMapper::toCommentDto, Collectors.toList()) + )); + + List dates = bookingRepository.findLastAndNextDatesByOwnerId(ownerId); + Map datesMap = new HashMap<>(); + for (LastAndNextDate date : dates) { + datesMap.put(date.getItemId(), date); + } + + return items.stream() + .map(item -> { + ItemDto dto = toItemDto(item); + + LastAndNextDate bookingDates = datesMap.get(item.getId()); + if (bookingDates != null) { + dto.setLastBooking(bookingDates.getLastBooking()); + dto.setNextBooking(bookingDates.getNextBooking()); + } + + dto.setComments(commentsMap.getOrDefault(item.getId(), Collections.emptyList())); + + return dto; + }) + .collect(Collectors.toList()); } @Override public List getNecessaryItem(String text) { + if (text == null || text.isBlank()) { + return Collections.emptyList(); + } List itemDtoList = new ArrayList<>(); - for (Item item : inMemoryItemStorage.getNecessaryItem(text)) { + for (Item item : itemRepository.searchByNameOrDescription(text)) { itemDtoList.add(toItemDto(item)); } return itemDtoList; } -} + + @Override + public CommentDto addComment(Long userId, Long itemId, Comment comment) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с id " + userId + " не найден")); + + Item item = itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException("Предмет с id " + itemId + " не найден")); + + if (!bookingRepository.existsByItemIdAndBookerIdAndStatusIsAndEndBefore( + itemId, userId, BookingStatus.APPROVED, LocalDateTime.now())) { + throw new BadRequestException("Пользователь никогда не брал предмет в аренду"); + } + comment.setItem(item); + comment.setAuthor(user); + comment.setCreated(LocalDateTime.now()); + return toCommentDto(commentRepository.save(comment)); + } + + private ItemDto addCommentsToItem(ItemDto itemDto, Collection comments) { + Collection commentsDto = comments.stream() + .map(CommentMapper::toCommentDto) + .toList(); + itemDto.setComments(commentsDto); + return itemDto; + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/controller/UserController.java b/src/main/java/ru/practicum/shareit/user/controller/UserController.java index 1c1e928..d198bf1 100644 --- a/src/main/java/ru/practicum/shareit/user/controller/UserController.java +++ b/src/main/java/ru/practicum/shareit/user/controller/UserController.java @@ -1,11 +1,14 @@ package ru.practicum.shareit.user.controller; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.model.User; import ru.practicum.shareit.user.service.UserServiceImpl; import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.validation.OnCreate; +import ru.practicum.shareit.validation.OnUpdate; import java.util.Collection; @@ -22,13 +25,14 @@ public class UserController { private final UserServiceImpl userServiceImpl; @PostMapping - public UserDto addUser(@Valid @RequestBody final UserDto user) { + public UserDto addUser(@Validated(OnCreate.class) @RequestBody final UserDto user) { log.info("Пользователь добавлен"); return userServiceImpl.addUser(user); } @PatchMapping("/{userId}") - public UserDto updateUser(@PathVariable("userId") final Long userId, @Valid @RequestBody final UserDto userDto) { + public UserDto updateUser(@PathVariable("userId") final Long userId, + @Validated(OnUpdate.class) @RequestBody final UserDto userDto) { userDto.setId(userId); log.info("Пользователь обновлен"); return userServiceImpl.updateUser(userDto); @@ -41,7 +45,7 @@ public Collection getAllUsers() { } @GetMapping("/{userId}") - public UserDto getUser(@PathVariable("userId") final Long userId) { + public User getUser(@PathVariable("userId") final Long userId) { log.info("Польователь с id " + userId + " выведен"); return userServiceImpl.getUser(userId); } @@ -51,4 +55,4 @@ public void deleteUser(@PathVariable("userId") final Long userId) { log.info("Пользователь с id " + userId + " удален"); userServiceImpl.deleteUser(userId); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/dao/InMemoryUserStorage.java b/src/main/java/ru/practicum/shareit/user/dao/InMemoryUserStorage.java deleted file mode 100644 index 18c3226..0000000 --- a/src/main/java/ru/practicum/shareit/user/dao/InMemoryUserStorage.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.practicum.shareit.user.dao; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Repository; -import ru.practicum.shareit.user.model.User; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Slf4j -@Repository -@RequiredArgsConstructor -public class InMemoryUserStorage { - private final Map users = new HashMap<>(); - - private Long getNextId() { - long currentMaxId = users.keySet() - .stream() - .mapToLong(id -> id) - .max() - .orElse(0); - return ++currentMaxId; - } - - public User addUser(User user) { - user.setId(getNextId()); - users.put(user.getId(), user); - return user; - } - - public User updateUser(User newUser) { - User user = users.get(newUser.getId()); - if (newUser.getName() != null) { - user.setName(newUser.getName()); - } - if (newUser.getEmail() != null) { - user.setEmail(newUser.getEmail()); - } - return user; - } - - public List getAllUsers() { - return new ArrayList<>(users.values()); - } - - public User getUser(Long id) { - return users.get(id); - } - - public void deleteUser(Long id) { - users.remove(id); - } - - public Map getUsers() { - return users; - } -} diff --git a/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java b/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java new file mode 100644 index 0000000..6ee781a --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dao/UserRepository.java @@ -0,0 +1,9 @@ +package ru.practicum.shareit.user.dao; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.user.model.User; + +@Repository +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 b4707c7..6bc7abe 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -2,15 +2,23 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; +import ru.practicum.shareit.validation.OnCreate; +import ru.practicum.shareit.validation.OnUpdate; @Data @Valid @AllArgsConstructor public class UserDto { private Long id; + + @NotBlank(groups = OnCreate.class, message = "Имя пользователя не может быть пустым") private String name; - @Email(message = "Email should be valid") + + @NotNull(groups = OnCreate.class, message = "Почта не должна быть пустой") + @Email(groups = {OnCreate.class, OnUpdate.class}, message = "Почта должна быть в нужном формате") private String email; } diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java index 2db2df6..8905e5a 100644 --- a/src/main/java/ru/practicum/shareit/user/model/User.java +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -1,17 +1,28 @@ package ru.practicum.shareit.user.model; -import jakarta.validation.constraints.Email; +import jakarta.persistence.*; import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; /** * TODO Sprint add-controllers. */ -@Data +@Entity @AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Table(name = "users") public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "name") private String name; - @Email(message = "Email should be valid") + + @Column(name = "email", unique = true) private String email; } diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/src/main/java/ru/practicum/shareit/user/service/UserService.java index ccb07e8..5f500ad 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserService.java +++ b/src/main/java/ru/practicum/shareit/user/service/UserService.java @@ -1,10 +1,10 @@ package ru.practicum.shareit.user.service; import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; import java.util.List; - public interface UserService { UserDto addUser(UserDto userDto); @@ -13,7 +13,7 @@ public interface UserService { List getAllUsers(); - UserDto getUser(Long id); + User getUser(Long id); void deleteUser(Long id); -} +} \ 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 index 2a83d52..2876ea8 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -1,12 +1,11 @@ package ru.practicum.shareit.user.service; -import jakarta.validation.ValidationException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import ru.practicum.shareit.exceptions.BadRequestException; +import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.exceptions.NotFoundException; -import ru.practicum.shareit.user.dao.InMemoryUserStorage; +import ru.practicum.shareit.user.dao.UserRepository; import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.user.model.User; @@ -20,56 +19,49 @@ @Service @RequiredArgsConstructor public class UserServiceImpl implements UserService { - private final InMemoryUserStorage inMemoryUserStorage; + private final UserRepository userRepository; @Override + @Transactional public UserDto addUser(UserDto userDto) { User user = toUser(userDto); - if (user.getEmail() == null) { - log.warn("Некорректная почта"); - throw new BadRequestException("некорректный тип почты"); - } - for (User usr : inMemoryUserStorage.getUsers().values()) { - if (usr.getEmail().equals(user.getEmail())) { - log.warn("такая почта уже используется"); - throw new ValidationException("Нельзя добавить существующую почту"); - } - } - return toUserDto(inMemoryUserStorage.addUser((user))); + return toUserDto(userRepository.save(user)); } @Override + @Transactional public UserDto updateUser(UserDto userDto) { - for (User usr : inMemoryUserStorage.getUsers().values()) { - if (usr.getEmail().equals(userDto.getEmail())) { - log.warn("такая почта уже используется"); - throw new ValidationException("Нельзя добавить существующую почту"); - } - } - if (!inMemoryUserStorage.getUsers().containsKey(userDto.getId())) { + if (!userRepository.existsById(userDto.getId())) { log.warn("Пользователь с указанным id не найден"); throw new NotFoundException("Пользователь с id = " + userDto.getId() + " не найден"); } - User newUser = toUser(userDto); - return toUserDto(inMemoryUserStorage.updateUser(newUser)); + User newUser = getUser(userDto.getId()); + if (userDto.getName() != null) { + newUser.setName(userDto.getName()); + } + if (userDto.getEmail() != null) { + newUser.setEmail(userDto.getEmail()); + } + return toUserDto(userRepository.save(newUser)); } @Override public List getAllUsers() { List dtoList = new ArrayList<>(); - for (User user : inMemoryUserStorage.getAllUsers()) { + for (User user : userRepository.findAll()) { dtoList.add(toUserDto(user)); } return dtoList; } @Override - public UserDto getUser(Long id) { - return toUserDto(inMemoryUserStorage.getUser(id)); + public User getUser(Long id) { + return userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Пользователь с id = " + id + " не найден")); } @Override public void deleteUser(Long id) { - inMemoryUserStorage.deleteUser(id); + userRepository.deleteById(id); } -} +} \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 9e9bc4b..713b80d 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -6,8 +6,7 @@ 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 H2 DB -#spring.datasource.driverClassName -#spring.datasource.url -#spring.datasource.username -#spring.datasource.password +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.username=dbuser +spring.datasource.password=12345 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 51c5180..d95862f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,8 +7,12 @@ 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 Postgres DB -#spring.datasource.driverClassName -#spring.datasource.url -#spring.datasource.username -#spring.datasource.password +spring.datasource.driverClassName=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://0.0.0.0:5432/shareit +spring.datasource.username=dbuser +spring.datasource.password=12345 + +#spring.datasource.driverClassName=org.h2.Driver +#spring.datasource.url=jdbc:h2:mem:testdb +#spring.datasource.username=dbuser +#spring.datasource.password=12345 diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..3d56497 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,39 @@ +CREATE TABLE IF NOT EXISTS users ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + email VARCHAR(512) NOT NULL, + CONSTRAINT pk_user PRIMARY KEY (id), + CONSTRAINT UQ_USER_EMAIL UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS items ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + description VARCHAR(255) NOT NULL, + available BOOLEAN NOT NULL, + owner_id BIGINT NOT NULL, + CONSTRAINT pk_item PRIMARY KEY (id), + FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS bookings ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + start_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + end_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + item_id BIGINT NOT NULL, + booker_id BIGINT NOT NULL, + status VARCHAR(255) NOT NULL, + CONSTRAINT pk_booking PRIMARY KEY (id), + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, + FOREIGN KEY (booker_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS comments ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + item_id BIGINT NOT NULL, + author_id BIGINT NOT NULL, + content TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + 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