From 930754bda06dd7f526bff0f5f9d78d3716a79c80 Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Sat, 15 Nov 2025 17:48:06 +0300 Subject: [PATCH 1/9] add database in project --- docker-compose.yaml | 17 ++++++ pom.xml | 5 ++ .../resources/application-test.properties | 10 ++-- src/main/resources/application.properties | 10 ++-- src/main/resources/schema.sql | 58 +++++++++++++++++++ 5 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 docker-compose.yaml create mode 100644 src/main/resources/schema.sql diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..b155d4e --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,17 @@ +services: + db: + image: postgres:16.1 + container_name: postgres + ports: + - "5432:5432" + volumes: + - ./volumes/postgres:/var/lib/postgresql/data/ + environment: + - POSTGRES_DB=shareit + - POSTGRES_USER=sa + - POSTGRES_PASSWORD=password + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 \ No newline at end of file diff --git a/pom.xml b/pom.xml index b771773..10fe1c2 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,11 @@ true + + org.springframework.boot + spring-boot-starter-data-jpa + + org.postgresql postgresql diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 9e9bc4b..ac7ec0b 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -6,8 +6,8 @@ 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.url=jdbc:h2:mem:./db/shareit +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.sql.init.data-locations=classpath:data.sql diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 51c5180..9f62b8c 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.jpa.show-sql=true spring.sql.init.mode=always logging.level.org.springframework.orm.jpa=INFO @@ -7,8 +8,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 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://localhost:5432/shareit +spring.datasource.username=sa +spring.datasource.password=password \ 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..2513eec --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,58 @@ +--DROP TABLE IF EXISTS users; +--DROP TABLE IF EXISTS items; +--DROP TABLE IF EXISTS comments; +--DROP TABLE IF EXISTS bookings; +--DROP TABLE IF EXISTS requests; + +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 requests ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + description TEXT NOT NULL, + requestor_id BIGINT, + CONSTRAINT pk_request PRIMARY KEY (id), + FOREIGN KEY (requestor_id) REFERENCES users(id) ON DELETE CASCADE +); + + +CREATE TABLE IF NOT EXISTS items ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + description TEXT NOT NULL, + is_available BOOLEAN NOT NULL, + owner_id BIGINT, + request_id BIGINT, + CONSTRAINT pk_item PRIMARY KEY (id), + FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (request_id) REFERENCES requests(id) ON DELETE SET NULL +); + +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, + booker_id BIGINT, + status VARCHAR(32) 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, + text TEXT NOT NULL, + item_id BIGINT, + author_id BIGINT, + CONSTRAINT pk_comment PRIMARY KEY (id), + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, + FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE +); + + From 1d06646195fa246b8ba9e48c0ccd90c0c2889554 Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Sun, 16 Nov 2025 14:30:28 +0300 Subject: [PATCH 2/9] add ORM user realisation --- .../shareit/booking/model/Booking.java | 17 +++++ .../ru/practicum/shareit/item/model/Item.java | 15 ++++ .../shareit/item/service/ItemServiceImpl.java | 6 +- .../shareit/request/ItemRequest.java | 13 +++- .../request/mapper/ItemRequestMapper.java | 2 +- .../ru/practicum/shareit/user/model/User.java | 5 ++ .../repository/InMemoryUserRepository.java | 72 ------------------- .../user/repository/UserRepository.java | 15 +--- .../shareit/user/service/UserServiceImpl.java | 42 ++++++++--- src/main/resources/schema.sql | 1 + 10 files changed, 91 insertions(+), 97 deletions(-) delete mode 100644 src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java 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 1fd5353..2e56417 100644 --- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.booking.model; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; import lombok.Data; @@ -13,11 +14,27 @@ @Data @Builder @FieldDefaults(level = AccessLevel.PRIVATE) +@Entity +@Table(name = "bookings") public class Booking { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; + + @Column(name = "start_date") LocalDateTime start; + + @Column(name = "end_date") LocalDateTime end; + + @ManyToOne + @JoinColumn(name = "item_id") Item item; + + @ManyToOne + @JoinColumn(name = "booker_id") User booker; + + @Enumerated(EnumType.STRING) BookingStatus status; } 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 3896da0..f28c0c6 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.item.model; +import jakarta.persistence.*; import lombok.*; import lombok.experimental.FieldDefaults; import ru.practicum.shareit.request.ItemRequest; @@ -10,11 +11,25 @@ @AllArgsConstructor @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) +@Entity +@Table(name = "items") public class Item { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; + String name; + String description; + + @Column(name = "is_available") boolean available; + + @ManyToOne + @JoinColumn(name = "owner_id") User owner; + + @ManyToOne + @JoinColumn(name = "request_id") ItemRequest request; } 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 61c4b67..3d76061 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -34,7 +34,7 @@ public ItemDto getById(Long userId, Long itemId) { throwIfUserNotExist(userId); return itemRepository.getById(itemId) .map(ItemMapper::toItemDto) - .orElseThrow(() -> new NotFoundException("Вещь с ID: %s не найдена".formatted(itemId))); + .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найдена".formatted(itemId))); } @Override @@ -66,12 +66,12 @@ public Collection search(Long userId, String text) { } private User throwIfUserNotExist(Long id) { - return userRepository.getById(id) + return userRepository.findById(id) .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); } private Item throwIfItemNotExist(Long id) { return itemRepository.getById(id) - .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); + .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найден".formatted(id))); } } diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java index 614c368..59579d9 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -1,19 +1,30 @@ package ru.practicum.shareit.request; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; import lombok.Data; import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.user.model.User; import java.time.LocalDateTime; @Data @Builder @FieldDefaults(level = AccessLevel.PRIVATE) +@Entity +@Table(name = "requests") public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; + String description; - Long requesterId; + + @ManyToOne + @JoinColumn(name = "requestor_id") + User requester; + LocalDateTime created; } diff --git a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java index 15d8368..8239e03 100644 --- a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java +++ b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -20,7 +20,7 @@ public ItemRequest toItemRequest(ItemRequestDto itemRequestDto) { return ItemRequest.builder() .id(itemRequestDto.getId()) .description(itemRequestDto.getDescription()) - .requesterId(itemRequestDto.getId()) +// .requesterId(itemRequestDto.getId()) .created(itemRequestDto.getCreated()) .build(); } 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 8e078bf..cc902a2 100644 --- a/src/main/java/ru/practicum/shareit/user/model/User.java +++ b/src/main/java/ru/practicum/shareit/user/model/User.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.user.model; +import jakarta.persistence.*; import lombok.*; import lombok.experimental.FieldDefaults; @@ -8,7 +9,11 @@ @NoArgsConstructor @AllArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) +@Entity +@Table(name = "users") public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; String name; String email; diff --git a/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java deleted file mode 100644 index 0dd6b71..0000000 --- a/src/main/java/ru/practicum/shareit/user/repository/InMemoryUserRepository.java +++ /dev/null @@ -1,72 +0,0 @@ -package ru.practicum.shareit.user.repository; - -import org.springframework.stereotype.Repository; -import ru.practicum.shareit.exception.NotFoundException; -import ru.practicum.shareit.exception.DuplicateValidationException; -import ru.practicum.shareit.user.model.User; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Objects; -import java.util.Optional; - -@Repository -public class InMemoryUserRepository implements UserRepository { - - private final HashMap users = new HashMap<>(); - - @Override - public Collection getAll() { - return users.values(); - } - - @Override - public Optional getById(Long userId) { - return Optional.ofNullable(users.get(userId)); - } - - @Override - public User create(User user) { - user.setId(generateNextId()); - throwIfEmailTaken(user); - users.put(user.getId(), user); - return user; - } - - @Override - public User update(User userUpdate) { - User user = throwIfUserNotExist(userUpdate.getId()); - throwIfEmailTaken(userUpdate); - if (userUpdate.getName() != null) user.setName(userUpdate.getName()); - if (userUpdate.getEmail() != null) user.setEmail(userUpdate.getEmail()); - return user; - } - - @Override - public void delete(Long id) { - throwIfUserNotExist(id); - users.remove(id); - } - - private Long generateNextId() { - Long nextId = users.keySet().stream() - .max(Long::compareTo) - .orElse(0L); - return ++nextId; - } - - private void throwIfEmailTaken(User user) { - boolean isEmailTaken = users.values().stream() - .filter(existedUser -> !Objects.equals(existedUser.getId(), user.getId())) - .anyMatch(existedUser -> Objects.equals(existedUser.getEmail(), user.getEmail())); - if (isEmailTaken) { - throw new DuplicateValidationException("Пользователь с email: '%s' уже существует" - .formatted(user.getEmail())); - } - } - - private User throwIfUserNotExist(Long id) { - return getById(id) - .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); - } -} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java index 3648312..c79d1e1 100644 --- a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java +++ b/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -1,19 +1,10 @@ package ru.practicum.shareit.user.repository; +import org.springframework.data.jpa.repository.JpaRepository; import ru.practicum.shareit.user.model.User; -import java.util.Collection; import java.util.Optional; -public interface UserRepository { - - Collection getAll(); - - Optional getById(Long userId); - - User create(User user); - - User update(User user); - - void delete(Long id); +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); } 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 c7043cb..266f582 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exception.DuplicateValidationException; import ru.practicum.shareit.exception.NotFoundException; import ru.practicum.shareit.user.dto.UpdateUserDto; import ru.practicum.shareit.user.dto.UserDto; @@ -10,43 +12,67 @@ import ru.practicum.shareit.user.repository.UserRepository; import java.util.Collection; +import java.util.Objects; +import java.util.Optional; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class UserServiceImpl implements UserService { private final UserRepository userRepository; @Override public Collection getAll() { - return userRepository.getAll().stream() + return userRepository.findAll().stream() .map(UserMapper::toUserDto) .toList(); } @Override public UserDto getById(Long userId) { - return userRepository.getById(userId) + return userRepository.findById(userId) .map(UserMapper::toUserDto) .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(userId))); } @Override + @Transactional public UserDto create(UserDto user) { User newUser = UserMapper.toUser(user); - newUser = userRepository.create(newUser); - return UserMapper.toUserDto(newUser); + throwIfEmailTaken(newUser); + return UserMapper.toUserDto(userRepository.save(newUser)); } @Override + @Transactional public UserDto update(UpdateUserDto user) { - User updatedUser = UserMapper.toUser(user); - updatedUser = userRepository.update(updatedUser); - return UserMapper.toUserDto(updatedUser); + User updateUser = UserMapper.toUser(user); + User existUser = throwIfUserNotExist(updateUser.getId()); + throwIfEmailTaken(updateUser); + if (updateUser.getName() != null) existUser.setName(updateUser.getName()); + if (updateUser.getEmail() != null) existUser.setEmail(updateUser.getEmail()); + return UserMapper.toUserDto(userRepository.save(existUser)); } @Override + @Transactional public void delete(Long id) { - userRepository.delete(id); + userRepository.deleteById(id); + } + + private User throwIfUserNotExist(Long id) { + return userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); + } + + private void throwIfEmailTaken(User user) { + Optional userWithEmail = userRepository.findByEmail(user.getEmail()); + if (userWithEmail.isPresent()) { + if (!Objects.equals(user.getId(), userWithEmail.get().getId())) { + throw new DuplicateValidationException("Пользователь с email: '%s' уже существует" + .formatted(user.getEmail())); + } + } } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 2513eec..b26cbb6 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -16,6 +16,7 @@ CREATE TABLE IF NOT EXISTS requests ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, description TEXT NOT NULL, requestor_id BIGINT, + created TIMESTAMP, CONSTRAINT pk_request PRIMARY KEY (id), FOREIGN KEY (requestor_id) REFERENCES users(id) ON DELETE CASCADE ); From 00ccacefe227e103b0612260d25518ed7cf5457f Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Sun, 16 Nov 2025 23:18:03 +0300 Subject: [PATCH 3/9] add ORM to Item realisation --- .../shareit/item/ItemController.java | 5 +- .../shareit/item/mapper/ItemMapper.java | 2 +- .../ru/practicum/shareit/item/model/Item.java | 2 +- .../repository/InMemoryItemRepository.java | 132 +++++++++--------- .../item/repository/ItemRepository.java | 36 +++-- .../shareit/item/service/ItemService.java | 2 +- .../shareit/item/service/ItemServiceImpl.java | 36 +++-- src/main/resources/schema.sql | 8 +- 8 files changed, 124 insertions(+), 99 deletions(-) diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index d024aa8..47489fb 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -43,9 +43,8 @@ public ItemDto update(@RequestHeader(USER_ID_HEADER) Long userId, } @GetMapping("/search") - public Collection search(@RequestHeader(USER_ID_HEADER) Long userId, - @RequestParam(name = "text") String text) { - return itemService.search(userId, text); + public Collection search(@RequestParam(name = "text") String text) { + return itemService.search(text); } } diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java index cf7c9a0..6cb5dbb 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -22,7 +22,7 @@ public Item toItem(ItemDto itemDto) { .id(itemDto.getId()) .name(itemDto.getName()) .description(itemDto.getDescription()) - .available(itemDto.getAvailable()) + .isAvailable(itemDto.getAvailable()) .build(); } } 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 f28c0c6..4fe84d6 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -23,7 +23,7 @@ public class Item { String description; @Column(name = "is_available") - boolean available; + boolean isAvailable; @ManyToOne @JoinColumn(name = "owner_id") diff --git a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java index 293cde5..e65882a 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java @@ -1,66 +1,66 @@ -package ru.practicum.shareit.item.repository; - -import org.springframework.stereotype.Repository; -import ru.practicum.shareit.item.dto.UpdateItemDto; -import ru.practicum.shareit.item.model.Item; - -import java.util.*; - -@Repository -public class InMemoryItemRepository implements ItemRepository { - - private final HashMap items = new HashMap<>(); - - @Override - public Optional getById(Long itemId) { - return Optional.ofNullable(items.get(itemId)); - } - - @Override - public Collection getByUserId(Long id) { - return items.values().stream() - .filter(item -> Objects.equals(item.getOwner().getId(), id)) - .toList(); - } - - @Override - public Item create(Item item) { - item.setId(generateNextId()); - items.put(item.getId(), item); - return item; - } - - @Override - public Item update(Item item, UpdateItemDto updateItem) { - if (updateItem.getName() != null && !updateItem.getName().isBlank()) { - item.setName(updateItem.getName()); - } - - if (updateItem.getDescription() != null && !updateItem.getDescription().isBlank()) { - item.setDescription(updateItem.getDescription()); - } - - if (updateItem.getAvailable() != null) { - item.setAvailable(updateItem.getAvailable()); - } - return item; - } - - @Override - public Collection searchItems(String text) { - if (text.isBlank()) return Collections.emptyList(); - - return items.values().stream() - .filter(Item::isAvailable) - .filter(item -> item.getName().toLowerCase().contains(text.toLowerCase()) || - item.getDescription().toLowerCase().contains(text.toLowerCase())) - .toList(); - } - - private Long generateNextId() { - Long nextId = items.keySet().stream() - .max(Long::compareTo) - .orElse(0L); - return ++nextId; - } -} +//package ru.practicum.shareit.item.repository; +// +//import org.springframework.stereotype.Repository; +//import ru.practicum.shareit.item.dto.UpdateItemDto; +//import ru.practicum.shareit.item.model.Item; +// +//import java.util.*; +// +//@Repository +//public class InMemoryItemRepository implements ItemRepository { +// +// private final HashMap items = new HashMap<>(); +// +// @Override +// public Optional getById(Long itemId) { +// return Optional.ofNullable(items.get(itemId)); +// } +// +// @Override +// public Collection getByUserId(Long id) { +// return items.values().stream() +// .filter(item -> Objects.equals(item.getOwner().getId(), id)) +// .toList(); +// } +// +// @Override +// public Item create(Item item) { +// item.setId(generateNextId()); +// items.put(item.getId(), item); +// return item; +// } +// +// @Override +// public Item update(Item item, UpdateItemDto updateItem) { +// if (updateItem.getName() != null && !updateItem.getName().isBlank()) { +// item.setName(updateItem.getName()); +// } +// +// if (updateItem.getDescription() != null && !updateItem.getDescription().isBlank()) { +// item.setDescription(updateItem.getDescription()); +// } +// +// if (updateItem.getAvailable() != null) { +// item.setAvailable(updateItem.getAvailable()); +// } +// return item; +// } +// +// @Override +// public Collection searchItems(String text) { +// if (text.isBlank()) return Collections.emptyList(); +// +// return items.values().stream() +// .filter(Item::isAvailable) +// .filter(item -> item.getName().toLowerCase().contains(text.toLowerCase()) || +// item.getDescription().toLowerCase().contains(text.toLowerCase())) +// .toList(); +// } +// +// private Long generateNextId() { +// Long nextId = items.keySet().stream() +// .max(Long::compareTo) +// .orElse(0L); +// return ++nextId; +// } +//} 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 e016f1b..d7dff38 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -1,20 +1,30 @@ package ru.practicum.shareit.item.repository; -import ru.practicum.shareit.item.dto.UpdateItemDto; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import ru.practicum.shareit.item.model.Item; import java.util.Collection; -import java.util.Optional; -public interface ItemRepository { - - Optional getById(Long itemId); - - Item create(Item item); - - Item update(Item item, UpdateItemDto updateItem); - - Collection getByUserId(Long id); - - Collection searchItems(String text); +public interface ItemRepository extends JpaRepository { + + Collection findAllByOwnerId(Long userId); + + @Query(""" + select i from Item i + where i.isAvailable = true + and upper(i.name) like upper(concat('%', ?1, '%')) + or upper(i.description) like upper(concat('%', ?1, '%')) + """) + Collection search(String text); + +// Optional getById(Long itemId); +// +// Item create(Item item); +// +// Item update(Item item, UpdateItemDto updateItem); +// +// Collection getByUserId(Long id); +// +// Collection searchItems(String text); } 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 bd796f8..b492f9e 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -15,5 +15,5 @@ public interface ItemService { ItemDto update(Long userId, Long itemId, UpdateItemDto item); - Collection search(Long userId, String text); + Collection search(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 3d76061..bb4acc4 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -2,6 +2,7 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.exception.ForbiddenException; import ru.practicum.shareit.exception.NotFoundException; import ru.practicum.shareit.item.dto.ItemDto; @@ -13,9 +14,11 @@ import ru.practicum.shareit.user.repository.UserRepository; import java.util.Collection; +import java.util.Collections; @Service @AllArgsConstructor +@Transactional(readOnly = true) public class ItemServiceImpl implements ItemService { private final ItemRepository itemRepository; @@ -24,7 +27,7 @@ public class ItemServiceImpl implements ItemService { @Override public Collection getAllByUser(Long userId) { throwIfUserNotExist(userId); - return itemRepository.getByUserId(userId).stream() + return itemRepository.findAllByOwnerId(userId).stream() .map(ItemMapper::toItemDto) .toList(); } @@ -32,35 +35,50 @@ public Collection getAllByUser(Long userId) { @Override public ItemDto getById(Long userId, Long itemId) { throwIfUserNotExist(userId); - return itemRepository.getById(itemId) + return itemRepository.findById(itemId) .map(ItemMapper::toItemDto) .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найдена".formatted(itemId))); } @Override + @Transactional public ItemDto create(Long userId, ItemDto item) { User owner = throwIfUserNotExist(userId); Item newItem = ItemMapper.toItem(item); newItem.setOwner(owner); - newItem = itemRepository.create(newItem); + newItem = itemRepository.save(newItem); return ItemMapper.toItemDto(newItem); } @Override + @Transactional public ItemDto update(Long userId, Long itemId, UpdateItemDto updateItem) { throwIfUserNotExist(userId); Item item = throwIfItemNotExist(itemId); if (!userId.equals(item.getOwner().getId())) { throw new ForbiddenException("Обновлять вещь может только ее владелец."); } - Item updatedItem = itemRepository.update(item, updateItem); - return ItemMapper.toItemDto(updatedItem); + if (updateItem.getName() != null && !updateItem.getName().isBlank()) { + item.setName(updateItem.getName()); + } + + if (updateItem.getDescription() != null && !updateItem.getDescription().isBlank()) { + item.setDescription(updateItem.getDescription()); + } + + if (updateItem.getAvailable() != null) { + item.setAvailable(updateItem.getAvailable()); + } + + return ItemMapper.toItemDto(itemRepository.save(item)); } @Override - public Collection search(Long userId, String text) { - throwIfUserNotExist(userId); - return itemRepository.searchItems(text).stream() + public Collection search(String text) { + if (text == null || text.isBlank()) { + return Collections.emptyList(); + } + return itemRepository.search(text).stream() .map(ItemMapper::toItemDto) .toList(); } @@ -71,7 +89,7 @@ private User throwIfUserNotExist(Long id) { } private Item throwIfItemNotExist(Long id) { - return itemRepository.getById(id) + return itemRepository.findById(id) .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найден".formatted(id))); } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index b26cbb6..38bfa6c 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,8 +1,8 @@ ---DROP TABLE IF EXISTS users; ---DROP TABLE IF EXISTS items; --DROP TABLE IF EXISTS comments; --DROP TABLE IF EXISTS bookings; +--DROP TABLE IF EXISTS items; --DROP TABLE IF EXISTS requests; +--DROP TABLE IF EXISTS users; CREATE TABLE IF NOT EXISTS users ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, @@ -54,6 +54,4 @@ CREATE TABLE IF NOT EXISTS comments ( CONSTRAINT pk_comment PRIMARY KEY (id), 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 From fb4ecda2af08e39c7108d1200e02b78a4821e8b6 Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Wed, 19 Nov 2025 19:39:40 +0300 Subject: [PATCH 4/9] add booking feature --- .../shareit/booking/BookingController.java | 52 ++++++++- .../shareit/booking/BookingState.java | 29 +++++ .../shareit/booking/dto/BookingDto.java | 8 +- .../booking/dto/RequestBookingDto.java | 19 ++++ .../shareit/booking/mapper/BookingMapper.java | 37 ++++++ .../shareit/booking/model/Booking.java | 6 +- .../booking/repository/BookingRepository.java | 10 ++ .../booking/service/BookingService.java | 19 ++++ .../booking/service/BookingServiceImpl.java | 107 ++++++++++++++++++ .../shareit/exception/ExceptionsHandler.java | 7 ++ .../exception/ValidationException.java | 7 ++ .../shareit/item/ItemController.java | 3 +- .../shareit/item/dto/ShortItemDto.java | 11 ++ .../shareit/user/dto/ShortUserDto.java | 10 ++ .../ru/practicum/shareit/util/Constants.java | 6 + 15 files changed, 318 insertions(+), 13 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/booking/BookingState.java create mode 100644 src/main/java/ru/practicum/shareit/booking/dto/RequestBookingDto.java create mode 100644 src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java create mode 100644 src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java create mode 100644 src/main/java/ru/practicum/shareit/booking/service/BookingService.java create mode 100644 src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java create mode 100644 src/main/java/ru/practicum/shareit/exception/ValidationException.java create mode 100644 src/main/java/ru/practicum/shareit/item/dto/ShortItemDto.java create mode 100644 src/main/java/ru/practicum/shareit/user/dto/ShortUserDto.java create mode 100644 src/main/java/ru/practicum/shareit/util/Constants.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java index b94493d..efeecbb 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,12 +1,54 @@ 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.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.RequestBookingDto; +import ru.practicum.shareit.booking.service.BookingService; + +import java.util.Collection; + +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; -/** - * TODO Sprint add-bookings. - */ @RestController @RequestMapping(path = "/bookings") +@RequiredArgsConstructor public class BookingController { + + private final BookingService bookingService; + + @PostMapping + public BookingDto create(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestBody RequestBookingDto booking) { + return bookingService.create(userId, booking); + } + + @PatchMapping("/{bookingId}") + public BookingDto updateStatus(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long bookingId, + @RequestParam Boolean approved) { + return bookingService.updateStatus(userId, bookingId, approved); + } + + @GetMapping("/{bookingId}") + public BookingDto getById(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long bookingId) { + return bookingService.getById(userId, bookingId); + } + + @GetMapping + public Collection getAllByBooker(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "ALL") String state) { + return bookingService.getAllByBooker(userId, state); + } + + @GetMapping("/owner") + public Collection getAllByOwner(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "ALL") String state) { + return bookingService.getAllByOwner(userId, state); + } + + + + } diff --git a/src/main/java/ru/practicum/shareit/booking/BookingState.java b/src/main/java/ru/practicum/shareit/booking/BookingState.java new file mode 100644 index 0000000..db2af19 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/BookingState.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.booking; + +import lombok.Getter; +import ru.practicum.shareit.exception.ValidationException; + +import java.util.Arrays; + +@Getter +public enum BookingState { + ALL("ALL"), + CURRENT("CURRENT"), + PAST("PAST"), + FUTURE("FUTURE"), + WAITING("WAITING"), + REJECTED("REJECTED"); + + private final String value; + + BookingState(String value) { + this.value = value; + } + + public static BookingState from(String text) { + return Arrays.stream(values()) + .filter(s -> s.value.equalsIgnoreCase(text)) + .findFirst() + .orElseThrow(() -> new ValidationException("Неизвестный статус бронирования: " + text)); + } +} 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 848cf61..5f8db58 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -5,8 +5,8 @@ import lombok.Data; import lombok.experimental.FieldDefaults; import ru.practicum.shareit.booking.BookingStatus; -import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.item.dto.ShortItemDto; +import ru.practicum.shareit.user.dto.ShortUserDto; import java.time.LocalDateTime; @@ -17,7 +17,7 @@ public class BookingDto { Long id; LocalDateTime start; LocalDateTime end; - Item item; - User booker; + ShortItemDto item; + ShortUserDto booker; BookingStatus status; } diff --git a/src/main/java/ru/practicum/shareit/booking/dto/RequestBookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/RequestBookingDto.java new file mode 100644 index 0000000..2df9014 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/dto/RequestBookingDto.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.booking.dto; + +import jakarta.validation.constraints.FutureOrPresent; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class RequestBookingDto { + Long itemId; + @FutureOrPresent + LocalDateTime start; + LocalDateTime end; +} diff --git a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java new file mode 100644 index 0000000..b539c27 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java @@ -0,0 +1,37 @@ +package ru.practicum.shareit.booking.mapper; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.RequestBookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.item.dto.ShortItemDto; +import ru.practicum.shareit.user.dto.ShortUserDto; + +@UtilityClass +public class BookingMapper { + + public static BookingDto toBookingDto(Booking booking) { + return BookingDto.builder() + .id(booking.getId()) + .start(booking.getStart()) + .end(booking.getEnd()) + .item(ShortItemDto.builder() + .id(booking.getItem().getId()) + .name(booking.getItem().getName()) + .build()) +// .itemId(booking.getItem() == null ? null : booking.getItem().getId()) + .booker(ShortUserDto.builder() + .id(booking.getBooker().getId()) + .build()) +// .bookerId(booking.getBooker() == null ? null : booking.getBooker().getId()) + .status(booking.getStatus()) + .build(); + } + + public static Booking toBookingFromRequest(RequestBookingDto bookingDto) { + return Booking.builder() + .start(bookingDto.getStart()) + .end(bookingDto.getEnd()) + .build(); + } +} 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 2e56417..adaec90 100644 --- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java +++ b/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -1,9 +1,7 @@ package ru.practicum.shareit.booking.model; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Data; +import lombok.*; import lombok.experimental.FieldDefaults; import ru.practicum.shareit.booking.BookingStatus; import ru.practicum.shareit.item.model.Item; @@ -15,6 +13,8 @@ @Builder @FieldDefaults(level = AccessLevel.PRIVATE) @Entity +@NoArgsConstructor +@AllArgsConstructor @Table(name = "bookings") public class Booking { @Id diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java new file mode 100644 index 0000000..7cbaef7 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.booking.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.booking.model.Booking; + +import java.util.Collection; + +public interface BookingRepository extends JpaRepository { + Collection findAllByBookerIdOrderByStartDesc(Long bookerId); +} 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..3c05348 --- /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.dto.RequestBookingDto; + +import java.util.Collection; + +public interface BookingService { + + BookingDto create(Long userId, RequestBookingDto booking); + + BookingDto updateStatus(Long userId, Long bookingId, Boolean approved); + + BookingDto getById(Long userId, Long bookingId); + + Collection getAllByBooker(Long userId, String state); + + Collection getAllByOwner(Long userId, 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..5012b62 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -0,0 +1,107 @@ +package ru.practicum.shareit.booking.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.BookingState; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.RequestBookingDto; +import ru.practicum.shareit.booking.mapper.BookingMapper; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.exception.ForbiddenException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class BookingServiceImpl implements BookingService { + + private final BookingRepository bookingRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + + @Override + @Transactional + public BookingDto create(Long userId, RequestBookingDto bookingRequest) { + User booker = throwIfUserNotExist(userId); + Item item = throwIfItemNotExist(bookingRequest.getItemId()); + if (!item.isAvailable()) { + throw new ValidationException("Предмет уже забронирован"); + } + Booking booking = BookingMapper.toBookingFromRequest(bookingRequest); + booking.setBooker(booker); + booking.setItem(item); + booking.setStatus(BookingStatus.WAITING); + return BookingMapper.toBookingDto(bookingRepository.save(booking)); + } + + @Override + @Transactional + public BookingDto updateStatus(Long userId, Long bookingId, Boolean approved) { + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Бронирование с номером: %s не найдено".formatted(bookingId))); + if (!Objects.equals(userId, booking.getItem().getOwner().getId())) { + throw new ForbiddenException("Изменить статус бронирования может только владелец предмета"); + } + + booking.setStatus(approved ? BookingStatus.APPROVED : BookingStatus.REJECTED); + return BookingMapper.toBookingDto(booking); + } + + @Override + public BookingDto getById(Long userId, Long bookingId) { + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Бронирование с номером: %s не найдено".formatted(bookingId))); + + if (!Objects.equals(booking.getBooker().getId(), userId) && + !Objects.equals(booking.getItem().getOwner().getId(), userId)) { + throw new ForbiddenException("Запросить информацию о бронировании может только создатель брони или " + + "владелец предмета"); + } + + return BookingMapper.toBookingDto(booking); + } + + @Override + public Collection getAllByBooker(Long userId, String state) { + throwIfUserNotExist(userId); + BookingState bookingState = BookingState.from(state); + Collection bookings; + switch (bookingState) { + case ALL -> bookings = bookingRepository.findAllByBookerIdOrderByStartDesc(userId); + default -> bookings = Collections.emptyList(); + } + + return bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + } + + @Override + public Collection getAllByOwner(Long userId, String state) { + BookingState bookingState = BookingState.from(state); + return List.of(); + } + + private User throwIfUserNotExist(Long id) { + return userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); + } + + private Item throwIfItemNotExist(Long id) { + return itemRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найден".formatted(id))); + } +} diff --git a/src/main/java/ru/practicum/shareit/exception/ExceptionsHandler.java b/src/main/java/ru/practicum/shareit/exception/ExceptionsHandler.java index 3f9689f..c84e105 100644 --- a/src/main/java/ru/practicum/shareit/exception/ExceptionsHandler.java +++ b/src/main/java/ru/practicum/shareit/exception/ExceptionsHandler.java @@ -31,5 +31,12 @@ public ErrorResponse handleForbiddenError(ForbiddenException exception) { return new ErrorResponse("Доступ запрещен:", exception.getMessage()); } + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleValidationError(ValidationException exception) { + log.error("Ошибка валидации данных: {}", exception.getMessage()); + return new ErrorResponse("Ошибка запроса: ", exception.getMessage()); + } + public record ErrorResponse(String error, String details) {} } diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/src/main/java/ru/practicum/shareit/exception/ValidationException.java new file mode 100644 index 0000000..59043da --- /dev/null +++ b/src/main/java/ru/practicum/shareit/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index 47489fb..2028e36 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -10,12 +10,13 @@ import java.util.Collection; +import static ru.practicum.shareit.util.Constants.USER_ID_HEADER; + @RestController @RequestMapping("/items") @AllArgsConstructor public class ItemController { - private static final String USER_ID_HEADER = "X-Sharer-User-Id"; private final ItemService itemService; @GetMapping diff --git a/src/main/java/ru/practicum/shareit/item/dto/ShortItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ShortItemDto.java new file mode 100644 index 0000000..2ecadbf --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/ShortItemDto.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.item.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ShortItemDto { + private Long id; + private String name; +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/ShortUserDto.java b/src/main/java/ru/practicum/shareit/user/dto/ShortUserDto.java new file mode 100644 index 0000000..707cacb --- /dev/null +++ b/src/main/java/ru/practicum/shareit/user/dto/ShortUserDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.user.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ShortUserDto { + private Long id; +} diff --git a/src/main/java/ru/practicum/shareit/util/Constants.java b/src/main/java/ru/practicum/shareit/util/Constants.java new file mode 100644 index 0000000..828dd22 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/util/Constants.java @@ -0,0 +1,6 @@ +package ru.practicum.shareit.util; + +public class Constants { + + public static final String USER_ID_HEADER = "X-Sharer-User-Id"; +} From 11e4b8f7b8c5ea05d8e50e56f9afd657f7a0263f Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Thu, 20 Nov 2025 21:19:22 +0300 Subject: [PATCH 5/9] add bookings search methods --- .../shareit/booking/BookingController.java | 3 -- .../booking/repository/BookingRepository.java | 35 +++++++++++++++++++ .../booking/service/BookingServiceImpl.java | 34 ++++++++++++++---- .../ru/practicum/shareit/util/Constants.java | 4 +++ 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/src/main/java/ru/practicum/shareit/booking/BookingController.java index efeecbb..b4de626 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -48,7 +48,4 @@ public Collection getAllByOwner(@RequestHeader(USER_ID_HEADER) Long return bookingService.getAllByOwner(userId, state); } - - - } diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java index 7cbaef7..732ab58 100644 --- a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -1,10 +1,45 @@ package ru.practicum.shareit.booking.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.shareit.booking.BookingStatus; import ru.practicum.shareit.booking.model.Booking; +import java.time.LocalDateTime; import java.util.Collection; public interface BookingRepository extends JpaRepository { Collection findAllByBookerIdOrderByStartDesc(Long bookerId); + + Collection findAllByBookerIdAndStatusOrderByStartDesc(Long bookerId, BookingStatus status); + + Collection findAllByBookerIdAndEndBeforeOrderByStartDesc(Long bookerId, LocalDateTime dateTime); + + Collection findAllByBookerIdAndStartAfterOrderByStartDesc(Long bookerId, LocalDateTime dateTime); + + @Query(""" + SELECT b FROM Booking b + WHERE b.booker.id = :bookerId + AND b.start <= CURRENT_TIMESTAMP + AND b.end >= CURRENT_TIMESTAMP + ORDER BY b.start DESC + """) + Collection findAllCurrentBookingsByBookerId(Long bookerId); + + Collection findAllByItemOwnerIdOrderByStartDesc(Long ownerId); + + Collection findAllByItemOwnerIdAndStatusOrderByStartDesc(Long bookerId, BookingStatus status); + + Collection findAllByItemOwnerIdAndEndBeforeOrderByStartDesc(Long bookerId, LocalDateTime dateAfterEnd); + + Collection findAllByItemOwnerIdAndStartAfterOrderByStartDesc(Long bookerId, LocalDateTime dateBeforeStart); + + @Query(""" + SELECT b FROM Booking b + WHERE b.item.owner.id = :ownerId + AND b.start <= CURRENT_TIMESTAMP + AND b.end >= CURRENT_TIMESTAMP + ORDER BY b.start DESC + """) + Collection findAllCurrentBookingsByItemOwnerId(Long ownerId); } diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java index 5012b62..c7f9f04 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -20,9 +20,10 @@ import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Objects; +import static ru.practicum.shareit.util.Constants.NOW; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -78,11 +79,16 @@ public BookingDto getById(Long userId, Long bookingId) { public Collection getAllByBooker(Long userId, String state) { throwIfUserNotExist(userId); BookingState bookingState = BookingState.from(state); - Collection bookings; - switch (bookingState) { - case ALL -> bookings = bookingRepository.findAllByBookerIdOrderByStartDesc(userId); - default -> bookings = Collections.emptyList(); - } + Collection bookings = switch (bookingState) { + case ALL -> bookingRepository.findAllByBookerIdOrderByStartDesc(userId); + case WAITING -> bookingRepository + .findAllByBookerIdAndStatusOrderByStartDesc(userId, BookingStatus.WAITING); + case REJECTED -> bookingRepository + .findAllByBookerIdAndStatusOrderByStartDesc(userId, BookingStatus.REJECTED); + case PAST -> bookingRepository.findAllByBookerIdAndEndBeforeOrderByStartDesc(userId, NOW); + case FUTURE -> bookingRepository.findAllByBookerIdAndStartAfterOrderByStartDesc(userId, NOW); + case CURRENT -> bookingRepository.findAllCurrentBookingsByBookerId(userId); + }; return bookings.stream() .map(BookingMapper::toBookingDto) @@ -91,8 +97,22 @@ public Collection getAllByBooker(Long userId, String state) { @Override public Collection getAllByOwner(Long userId, String state) { + throwIfUserNotExist(userId); BookingState bookingState = BookingState.from(state); - return List.of(); + Collection bookings = switch (bookingState) { + case ALL -> bookingRepository.findAllByItemOwnerIdOrderByStartDesc(userId); + case WAITING -> bookingRepository + .findAllByItemOwnerIdAndStatusOrderByStartDesc(userId, BookingStatus.WAITING); + case REJECTED -> bookingRepository + .findAllByItemOwnerIdAndStatusOrderByStartDesc(userId, BookingStatus.REJECTED); + case PAST -> bookingRepository.findAllByItemOwnerIdAndEndBeforeOrderByStartDesc(userId, NOW); + case FUTURE -> bookingRepository.findAllByItemOwnerIdAndStartAfterOrderByStartDesc(userId, NOW); + case CURRENT -> bookingRepository.findAllCurrentBookingsByItemOwnerId(userId); + }; + + return bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); } private User throwIfUserNotExist(Long id) { diff --git a/src/main/java/ru/practicum/shareit/util/Constants.java b/src/main/java/ru/practicum/shareit/util/Constants.java index 828dd22..7793f44 100644 --- a/src/main/java/ru/practicum/shareit/util/Constants.java +++ b/src/main/java/ru/practicum/shareit/util/Constants.java @@ -1,6 +1,10 @@ package ru.practicum.shareit.util; +import java.time.LocalDateTime; + public class Constants { public static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + public static final LocalDateTime NOW = LocalDateTime.now(); } From 9959dc83c440750f76d8be02556013a5336eb757 Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Sat, 22 Nov 2025 11:45:03 +0300 Subject: [PATCH 6/9] add last and next bookings --- .../shareit/booking/dto/ShortBookingDto.java | 18 +++++ .../shareit/booking/mapper/BookingMapper.java | 10 ++- .../booking/repository/BookingRepository.java | 10 +++ .../shareit/item/ItemController.java | 4 +- .../shareit/item/dto/ItemDtoWithBookings.java | 25 +++++++ .../shareit/item/mapper/ItemMapper.java | 35 ++++++++++ .../repository/InMemoryItemRepository.java | 66 ------------------- .../item/repository/ItemRepository.java | 18 ++--- .../shareit/item/service/ItemService.java | 3 +- .../shareit/item/service/ItemServiceImpl.java | 24 ++++++- 10 files changed, 125 insertions(+), 88 deletions(-) create mode 100644 src/main/java/ru/practicum/shareit/booking/dto/ShortBookingDto.java create mode 100644 src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookings.java delete mode 100644 src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/ShortBookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/ShortBookingDto.java new file mode 100644 index 0000000..abc5efb --- /dev/null +++ b/src/main/java/ru/practicum/shareit/booking/dto/ShortBookingDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.booking.dto; + +import jakarta.validation.constraints.FutureOrPresent; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ShortBookingDto { + @FutureOrPresent + LocalDateTime start; + LocalDateTime end; +} diff --git a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java index b539c27..2fd635c 100644 --- a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java +++ b/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java @@ -3,6 +3,7 @@ import lombok.experimental.UtilityClass; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.dto.RequestBookingDto; +import ru.practicum.shareit.booking.dto.ShortBookingDto; import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.item.dto.ShortItemDto; import ru.practicum.shareit.user.dto.ShortUserDto; @@ -19,11 +20,9 @@ public static BookingDto toBookingDto(Booking booking) { .id(booking.getItem().getId()) .name(booking.getItem().getName()) .build()) -// .itemId(booking.getItem() == null ? null : booking.getItem().getId()) .booker(ShortUserDto.builder() .id(booking.getBooker().getId()) .build()) -// .bookerId(booking.getBooker() == null ? null : booking.getBooker().getId()) .status(booking.getStatus()) .build(); } @@ -34,4 +33,11 @@ public static Booking toBookingFromRequest(RequestBookingDto bookingDto) { .end(bookingDto.getEnd()) .build(); } + + public static ShortBookingDto toShortBookingDto(Booking booking) { + return ShortBookingDto.builder() + .start(booking.getStart()) + .end(booking.getEnd()) + .build(); + } } diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java index 732ab58..12b1c40 100644 --- a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -7,6 +7,7 @@ import java.time.LocalDateTime; import java.util.Collection; +import java.util.List; public interface BookingRepository extends JpaRepository { Collection findAllByBookerIdOrderByStartDesc(Long bookerId); @@ -42,4 +43,13 @@ public interface BookingRepository extends JpaRepository { ORDER BY b.start DESC """) Collection findAllCurrentBookingsByItemOwnerId(Long ownerId); + + @Query(""" + SELECT b FROM Booking b + WHERE b.item.id IN :itemIds + AND b.status = 'APPROVED' + AND (b.end < CURRENT_TIMESTAMP OR b.start > CURRENT_TIMESTAMP) + ORDER BY b.item.id, b.start + """) + Collection findAllBookingsForItems(List itemIds); } diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index 2028e36..b93b5ae 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -5,6 +5,7 @@ import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemDtoWithBookings; import ru.practicum.shareit.item.dto.UpdateItemDto; import ru.practicum.shareit.item.service.ItemService; @@ -20,7 +21,7 @@ public class ItemController { private final ItemService itemService; @GetMapping - public Collection getAllByUser(@RequestHeader(USER_ID_HEADER) Long userId) { + public Collection getAllByUser(@RequestHeader(USER_ID_HEADER) Long userId) { return itemService.getAllByUser(userId); } @@ -47,5 +48,4 @@ public ItemDto update(@RequestHeader(USER_ID_HEADER) Long userId, public Collection search(@RequestParam(name = "text") String text) { return itemService.search(text); } - } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookings.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookings.java new file mode 100644 index 0000000..ce7a01d --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookings.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.booking.dto.ShortBookingDto; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ItemDtoWithBookings { + Long id; + @NotBlank + String name; + @NotBlank + String description; + @NotNull + Boolean available; + Long requestId; + ShortBookingDto lastBooking; + ShortBookingDto nextBooking; +} diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java index 6cb5dbb..a7a3036 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -1,9 +1,16 @@ package ru.practicum.shareit.item.mapper; import lombok.experimental.UtilityClass; +import ru.practicum.shareit.booking.mapper.BookingMapper; +import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemDtoWithBookings; import ru.practicum.shareit.item.model.Item; +import java.util.List; + +import static ru.practicum.shareit.util.Constants.NOW; + @UtilityClass public class ItemMapper { @@ -25,4 +32,32 @@ public Item toItem(ItemDto itemDto) { .isAvailable(itemDto.getAvailable()) .build(); } + + public ItemDtoWithBookings toItemDtoWithBookings(Item item, List bookings) { + Booking lastBooking = null; + Booking nextBooking = null; + + if (bookings != null && !bookings.isEmpty()) { + + lastBooking = bookings.stream() + .filter(b -> b.getEnd().isBefore(NOW)) + .max((b1, b2) -> b1.getEnd().compareTo(b2.getEnd())) + .orElse(null); + + nextBooking = bookings.stream() + .filter(b -> b.getStart().isAfter(NOW)) + .min((b1, b2) -> b1.getStart().compareTo(b2.getStart())) + .orElse(null); + } + + return ItemDtoWithBookings.builder() + .id(item.getId()) + .name(item.getName()) + .description(item.getDescription()) + .available(item.isAvailable()) + .requestId(item.getRequest() == null ? null : item.getRequest().getId()) + .lastBooking(lastBooking == null ? null : BookingMapper.toShortBookingDto(lastBooking)) + .nextBooking(nextBooking == null ? null : BookingMapper.toShortBookingDto(nextBooking)) + .build(); + } } diff --git a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java b/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java deleted file mode 100644 index e65882a..0000000 --- a/src/main/java/ru/practicum/shareit/item/repository/InMemoryItemRepository.java +++ /dev/null @@ -1,66 +0,0 @@ -//package ru.practicum.shareit.item.repository; -// -//import org.springframework.stereotype.Repository; -//import ru.practicum.shareit.item.dto.UpdateItemDto; -//import ru.practicum.shareit.item.model.Item; -// -//import java.util.*; -// -//@Repository -//public class InMemoryItemRepository implements ItemRepository { -// -// private final HashMap items = new HashMap<>(); -// -// @Override -// public Optional getById(Long itemId) { -// return Optional.ofNullable(items.get(itemId)); -// } -// -// @Override -// public Collection getByUserId(Long id) { -// return items.values().stream() -// .filter(item -> Objects.equals(item.getOwner().getId(), id)) -// .toList(); -// } -// -// @Override -// public Item create(Item item) { -// item.setId(generateNextId()); -// items.put(item.getId(), item); -// return item; -// } -// -// @Override -// public Item update(Item item, UpdateItemDto updateItem) { -// if (updateItem.getName() != null && !updateItem.getName().isBlank()) { -// item.setName(updateItem.getName()); -// } -// -// if (updateItem.getDescription() != null && !updateItem.getDescription().isBlank()) { -// item.setDescription(updateItem.getDescription()); -// } -// -// if (updateItem.getAvailable() != null) { -// item.setAvailable(updateItem.getAvailable()); -// } -// return item; -// } -// -// @Override -// public Collection searchItems(String text) { -// if (text.isBlank()) return Collections.emptyList(); -// -// return items.values().stream() -// .filter(Item::isAvailable) -// .filter(item -> item.getName().toLowerCase().contains(text.toLowerCase()) || -// item.getDescription().toLowerCase().contains(text.toLowerCase())) -// .toList(); -// } -// -// private Long generateNextId() { -// Long nextId = items.keySet().stream() -// .max(Long::compareTo) -// .orElse(0L); -// return ++nextId; -// } -//} 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 d7dff38..24ba577 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -11,20 +11,10 @@ public interface ItemRepository extends JpaRepository { Collection findAllByOwnerId(Long userId); @Query(""" - select i from Item i - where i.isAvailable = true - and upper(i.name) like upper(concat('%', ?1, '%')) - or upper(i.description) like upper(concat('%', ?1, '%')) + SELECT i FROM Item i + WHERE i.isAvailable = true + AND (UPPER(i.name) LIKE UPPER(CONCAT('%', ?1, '%')) + OR UPPER(i.description) LIKE UPPER(CONCAT('%', ?1, '%'))) """) Collection search(String text); - -// Optional getById(Long itemId); -// -// Item create(Item item); -// -// Item update(Item item, UpdateItemDto updateItem); -// -// Collection getByUserId(Long id); -// -// Collection searchItems(String text); } 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 b492f9e..6cbf4ba 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -1,13 +1,14 @@ package ru.practicum.shareit.item.service; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemDtoWithBookings; import ru.practicum.shareit.item.dto.UpdateItemDto; import java.util.Collection; public interface ItemService { - Collection getAllByUser(Long userId); + Collection getAllByUser(Long userId); ItemDto getById(Long userId, Long id); 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 bb4acc4..c5acbab 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -3,9 +3,12 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.exception.ForbiddenException; import ru.practicum.shareit.exception.NotFoundException; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemDtoWithBookings; import ru.practicum.shareit.item.dto.UpdateItemDto; import ru.practicum.shareit.item.mapper.ItemMapper; import ru.practicum.shareit.item.model.Item; @@ -15,6 +18,9 @@ import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Service @AllArgsConstructor @@ -23,12 +29,24 @@ public class ItemServiceImpl implements ItemService { private final ItemRepository itemRepository; private final UserRepository userRepository; + private final BookingRepository bookingRepository; @Override - public Collection getAllByUser(Long userId) { + public Collection getAllByUser(Long userId) { throwIfUserNotExist(userId); - return itemRepository.findAllByOwnerId(userId).stream() - .map(ItemMapper::toItemDto) + Collection items = itemRepository.findAllByOwnerId(userId); + if (items.isEmpty()) { + return Collections.emptyList(); + } + List itemsIds = items.stream().map(Item::getId).toList(); + Collection bookings = bookingRepository.findAllBookingsForItems(itemsIds); + + Map> bookingsByItem = bookings.stream() + .collect(Collectors.groupingBy(booking -> booking.getItem().getId())); + + return items.stream() + .map(item -> ItemMapper.toItemDtoWithBookings(item, + bookingsByItem.getOrDefault(item.getId(), List.of()))) .toList(); } From c0181ccff7bcccca808e9ea4a26042d0ae122f00 Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Sun, 23 Nov 2025 09:22:19 +0300 Subject: [PATCH 7/9] add comment adding --- .../booking/repository/BookingRepository.java | 9 +++++ .../shareit/item/ItemController.java | 9 +++++ .../shareit/item/dto/CommentDto.java | 23 +++++++++++++ .../practicum/shareit/item/dto/ItemDto.java | 4 +++ .../shareit/item/mapper/CommentMapper.java | 28 ++++++++++++++++ .../practicum/shareit/item/model/Comment.java | 33 +++++++++++++++++++ .../item/repository/CommentRepository.java | 7 ++++ .../shareit/item/service/ItemService.java | 3 ++ .../shareit/item/service/ItemServiceImpl.java | 23 +++++++++++++ src/main/resources/schema.sql | 1 + 10 files changed, 140 insertions(+) create mode 100644 src/main/java/ru/practicum/shareit/item/dto/CommentDto.java create mode 100644 src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java create mode 100644 src/main/java/ru/practicum/shareit/item/model/Comment.java create mode 100644 src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java index 12b1c40..e31ba99 100644 --- a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -52,4 +52,13 @@ public interface BookingRepository extends JpaRepository { ORDER BY b.item.id, b.start """) Collection findAllBookingsForItems(List itemIds); + + @Query(""" + SELECT b FROM Booking b + WHERE b.item.id = :itemId + AND b.booker.id = :userId + AND b.status = 'APPROVED' + AND b.end < CURRENT_TIMESTAMP + """) + Collection findPastBookingsForUserByItemId(Long userId, Long itemId); } diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index b93b5ae..c88075a 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.Positive; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.dto.ItemDtoWithBookings; import ru.practicum.shareit.item.dto.UpdateItemDto; @@ -48,4 +49,12 @@ public ItemDto update(@RequestHeader(USER_ID_HEADER) Long userId, public Collection search(@RequestParam(name = "text") String text) { return itemService.search(text); } + + @PostMapping("{itemId}/comment") + public CommentDto addComment(@RequestHeader(USER_ID_HEADER) Long authorId, + @PathVariable @Positive Long itemId, + @RequestBody @Valid CommentDto commentRequest) { + return itemService.addComment(authorId, itemId, commentRequest); + } + } 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..35a2fa8 --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class CommentDto { + Long id; + + @NotNull + String text; + + String authorName; + + 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 949c43e..d9c64e8 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -12,11 +12,15 @@ @FieldDefaults(level = AccessLevel.PRIVATE) public class ItemDto { Long id; + @NotBlank String name; + @NotBlank String description; + @NotNull Boolean available; + Long requestId; } diff --git a/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java new file mode 100644 index 0000000..2832c4d --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.item.mapper; + +import lombok.experimental.UtilityClass; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.model.Comment; + +import static ru.practicum.shareit.util.Constants.NOW; + +@UtilityClass +public class CommentMapper { + + public CommentDto toCommentDto(Comment comment) { + return CommentDto.builder() + .id(comment.getId()) + .text(comment.getText()) + .authorName(comment.getAuthor().getName()) + .created(comment.getCreated()) + .build(); + } + + public Comment toComment(CommentDto commentDto) { + return Comment.builder() + .id(commentDto.getId()) + .text(commentDto.getText()) + .created(commentDto.getCreated() == null ? NOW : commentDto.getCreated()) + .build(); + } +} 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..959926b --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/model/Comment.java @@ -0,0 +1,33 @@ +package ru.practicum.shareit.item.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +@Entity +@Table(name = "comments") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + String text; + + @ManyToOne + @JoinColumn(name = "item_id") + Item item; + + @ManyToOne + @JoinColumn(name = "author_id") + User author; + + LocalDateTime created; +} 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..63277ce --- /dev/null +++ b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.item.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.item.model.Comment; + +public interface CommentRepository extends JpaRepository { +} 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 6cbf4ba..89c7b78 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -1,5 +1,6 @@ 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.dto.ItemDtoWithBookings; import ru.practicum.shareit.item.dto.UpdateItemDto; @@ -17,4 +18,6 @@ public interface ItemService { ItemDto update(Long userId, Long itemId, UpdateItemDto item); Collection search(String text); + + CommentDto addComment(Long authorId, Long itemId, CommentDto commentRequest); } 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 c5acbab..e951c17 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -7,11 +7,16 @@ import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.exception.ForbiddenException; import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.ValidationException; +import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.dto.ItemDtoWithBookings; import ru.practicum.shareit.item.dto.UpdateItemDto; +import ru.practicum.shareit.item.mapper.CommentMapper; import ru.practicum.shareit.item.mapper.ItemMapper; +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.model.User; import ru.practicum.shareit.user.repository.UserRepository; @@ -30,6 +35,7 @@ public class ItemServiceImpl implements ItemService { private final ItemRepository itemRepository; private final UserRepository userRepository; private final BookingRepository bookingRepository; + private final CommentRepository commentRepository; @Override public Collection getAllByUser(Long userId) { @@ -53,6 +59,7 @@ public Collection getAllByUser(Long userId) { @Override public ItemDto getById(Long userId, Long itemId) { throwIfUserNotExist(userId); + return itemRepository.findById(itemId) .map(ItemMapper::toItemDto) .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найдена".formatted(itemId))); @@ -101,6 +108,22 @@ public Collection search(String text) { .toList(); } + @Override + @Transactional + public CommentDto addComment(Long authorId, Long itemId, CommentDto commentRequest) { + Item item = throwIfItemNotExist(itemId); + User author = throwIfUserNotExist(authorId); + Collection pastUserBookings = bookingRepository.findPastBookingsForUserByItemId(authorId, itemId); + if (pastUserBookings.isEmpty()) { + throw new ValidationException("Пользователь не бронировал предмет с ID: %s".formatted(itemId)); + } + Comment comment = CommentMapper.toComment(commentRequest); + comment.setItem(item); + comment.setAuthor(author); + comment = commentRepository.save(comment); + return CommentMapper.toCommentDto(comment); + } + private User throwIfUserNotExist(Long id) { return userRepository.findById(id) .orElseThrow(() -> new NotFoundException("Пользователь с ID: %s не найден".formatted(id))); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 38bfa6c..ec40e52 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -51,6 +51,7 @@ CREATE TABLE IF NOT EXISTS comments ( text TEXT NOT NULL, item_id BIGINT, author_id BIGINT, + created TIMESTAMP, CONSTRAINT pk_comment PRIMARY KEY (id), FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE From 5fd6d026e6ee0b46d04e3e22879d3e3bbb59793f Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Sun, 23 Nov 2025 13:01:40 +0300 Subject: [PATCH 8/9] end comments and bookings logic --- .../booking/repository/BookingRepository.java | 9 +++++++ .../booking/service/BookingServiceImpl.java | 15 ++++++----- .../shareit/item/ItemController.java | 6 ++--- .../practicum/shareit/item/dto/ItemDto.java | 4 +++ ...va => ItemDtoWithBookingsAndComments.java} | 5 +++- .../shareit/item/mapper/CommentMapper.java | 4 +-- .../shareit/item/mapper/ItemMapper.java | 23 +++++++++------- .../item/repository/CommentRepository.java | 17 ++++++++++++ .../shareit/item/service/ItemService.java | 6 ++--- .../shareit/item/service/ItemServiceImpl.java | 26 +++++++++++++------ .../request/mapper/ItemRequestMapper.java | 1 - .../ru/practicum/shareit/util/Constants.java | 3 --- src/main/resources/schema.sql | 10 +++---- 13 files changed, 87 insertions(+), 42 deletions(-) rename src/main/java/ru/practicum/shareit/item/dto/{ItemDtoWithBookings.java => ItemDtoWithBookingsAndComments.java} (83%) diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java index e31ba99..8c1f2ff 100644 --- a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java +++ b/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java @@ -55,10 +55,19 @@ public interface BookingRepository extends JpaRepository { @Query(""" SELECT b FROM Booking b + JOIN FETCH b.item + JOIN FETCH b.booker WHERE b.item.id = :itemId AND b.booker.id = :userId AND b.status = 'APPROVED' AND b.end < CURRENT_TIMESTAMP """) Collection findPastBookingsForUserByItemId(Long userId, Long itemId); + + @Query(""" + SELECT b FROM Booking b + WHERE b.item.id = :itemId + AND b.status = 'APPROVED' + """) + List findAllByItemId(Long itemId); } diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java index c7f9f04..d588846 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -18,12 +18,10 @@ import ru.practicum.shareit.user.model.User; import ru.practicum.shareit.user.repository.UserRepository; +import java.time.LocalDateTime; import java.util.Collection; -import java.util.Collections; import java.util.Objects; -import static ru.practicum.shareit.util.Constants.NOW; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -85,8 +83,9 @@ public Collection getAllByBooker(Long userId, String state) { .findAllByBookerIdAndStatusOrderByStartDesc(userId, BookingStatus.WAITING); case REJECTED -> bookingRepository .findAllByBookerIdAndStatusOrderByStartDesc(userId, BookingStatus.REJECTED); - case PAST -> bookingRepository.findAllByBookerIdAndEndBeforeOrderByStartDesc(userId, NOW); - case FUTURE -> bookingRepository.findAllByBookerIdAndStartAfterOrderByStartDesc(userId, NOW); + case PAST -> bookingRepository.findAllByBookerIdAndEndBeforeOrderByStartDesc(userId, LocalDateTime.now()); + case FUTURE -> bookingRepository + .findAllByBookerIdAndStartAfterOrderByStartDesc(userId, LocalDateTime.now()); case CURRENT -> bookingRepository.findAllCurrentBookingsByBookerId(userId); }; @@ -105,8 +104,10 @@ public Collection getAllByOwner(Long userId, String state) { .findAllByItemOwnerIdAndStatusOrderByStartDesc(userId, BookingStatus.WAITING); case REJECTED -> bookingRepository .findAllByItemOwnerIdAndStatusOrderByStartDesc(userId, BookingStatus.REJECTED); - case PAST -> bookingRepository.findAllByItemOwnerIdAndEndBeforeOrderByStartDesc(userId, NOW); - case FUTURE -> bookingRepository.findAllByItemOwnerIdAndStartAfterOrderByStartDesc(userId, NOW); + case PAST -> bookingRepository + .findAllByItemOwnerIdAndEndBeforeOrderByStartDesc(userId, LocalDateTime.now()); + case FUTURE -> bookingRepository + .findAllByItemOwnerIdAndStartAfterOrderByStartDesc(userId, LocalDateTime.now()); case CURRENT -> bookingRepository.findAllCurrentBookingsByItemOwnerId(userId); }; diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java index c88075a..8b17d01 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemDtoWithBookings; +import ru.practicum.shareit.item.dto.ItemDtoWithBookingsAndComments; import ru.practicum.shareit.item.dto.UpdateItemDto; import ru.practicum.shareit.item.service.ItemService; @@ -22,12 +22,12 @@ public class ItemController { private final ItemService itemService; @GetMapping - public Collection getAllByUser(@RequestHeader(USER_ID_HEADER) Long userId) { + public Collection getAllByUser(@RequestHeader(USER_ID_HEADER) Long userId) { return itemService.getAllByUser(userId); } @GetMapping("/{itemId}") - public ItemDto getById(@RequestHeader(USER_ID_HEADER) Long userId, + public ItemDtoWithBookingsAndComments getById(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId) { return itemService.getById(userId, itemId); } 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 d9c64e8..4608dd9 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -7,6 +7,8 @@ import lombok.Data; import lombok.experimental.FieldDefaults; +import java.util.Collection; + @Data @Builder @FieldDefaults(level = AccessLevel.PRIVATE) @@ -23,4 +25,6 @@ public class ItemDto { Boolean available; Long requestId; + + Collection comments; } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookings.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookingsAndComments.java similarity index 83% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookings.java rename to src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookingsAndComments.java index ce7a01d..2823c73 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookings.java +++ b/src/main/java/ru/practicum/shareit/item/dto/ItemDtoWithBookingsAndComments.java @@ -8,10 +8,12 @@ import lombok.experimental.FieldDefaults; import ru.practicum.shareit.booking.dto.ShortBookingDto; +import java.util.Collection; + @Data @Builder @FieldDefaults(level = AccessLevel.PRIVATE) -public class ItemDtoWithBookings { +public class ItemDtoWithBookingsAndComments { Long id; @NotBlank String name; @@ -22,4 +24,5 @@ public class ItemDtoWithBookings { Long requestId; ShortBookingDto lastBooking; ShortBookingDto nextBooking; + Collection comments; } diff --git a/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java index 2832c4d..49055da 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java @@ -4,7 +4,7 @@ import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.model.Comment; -import static ru.practicum.shareit.util.Constants.NOW; +import java.time.LocalDateTime; @UtilityClass public class CommentMapper { @@ -22,7 +22,7 @@ public Comment toComment(CommentDto commentDto) { return Comment.builder() .id(commentDto.getId()) .text(commentDto.getText()) - .created(commentDto.getCreated() == null ? NOW : commentDto.getCreated()) + .created(commentDto.getCreated() == null ? LocalDateTime.now() : commentDto.getCreated()) .build(); } } diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java index a7a3036..b50085c 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -4,13 +4,15 @@ import ru.practicum.shareit.booking.mapper.BookingMapper; import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemDtoWithBookings; +import ru.practicum.shareit.item.dto.ItemDtoWithBookingsAndComments; +import ru.practicum.shareit.item.model.Comment; import ru.practicum.shareit.item.model.Item; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Comparator; import java.util.List; -import static ru.practicum.shareit.util.Constants.NOW; - @UtilityClass public class ItemMapper { @@ -33,24 +35,26 @@ public Item toItem(ItemDto itemDto) { .build(); } - public ItemDtoWithBookings toItemDtoWithBookings(Item item, List bookings) { + public ItemDtoWithBookingsAndComments toItemDtoWithBookingsAndComments(Item item, + List bookings, + Collection comments) { Booking lastBooking = null; Booking nextBooking = null; if (bookings != null && !bookings.isEmpty()) { lastBooking = bookings.stream() - .filter(b -> b.getEnd().isBefore(NOW)) - .max((b1, b2) -> b1.getEnd().compareTo(b2.getEnd())) + .filter(b -> b.getEnd().isBefore(LocalDateTime.now())) + .max(Comparator.comparing(Booking::getEnd)) .orElse(null); nextBooking = bookings.stream() - .filter(b -> b.getStart().isAfter(NOW)) - .min((b1, b2) -> b1.getStart().compareTo(b2.getStart())) + .filter(b -> b.getStart().isAfter(LocalDateTime.now())) + .min(Comparator.comparing(Booking::getStart)) .orElse(null); } - return ItemDtoWithBookings.builder() + return ItemDtoWithBookingsAndComments.builder() .id(item.getId()) .name(item.getName()) .description(item.getDescription()) @@ -58,6 +62,7 @@ public ItemDtoWithBookings toItemDtoWithBookings(Item item, List bookin .requestId(item.getRequest() == null ? null : item.getRequest().getId()) .lastBooking(lastBooking == null ? null : BookingMapper.toShortBookingDto(lastBooking)) .nextBooking(nextBooking == null ? null : BookingMapper.toShortBookingDto(nextBooking)) + .comments(comments.stream().map(CommentMapper::toCommentDto).toList()) .build(); } } diff --git a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java index 63277ce..5f101e6 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java +++ b/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java @@ -1,7 +1,24 @@ package ru.practicum.shareit.item.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import ru.practicum.shareit.item.model.Comment; +import java.util.Collection; +import java.util.List; + public interface CommentRepository extends JpaRepository { + @Query(""" + SELECT c FROM Comment c + JOIN FETCH c.author + WHERE c.item.id = :itemId + """) + Collection findAllByItemId(Long itemId); + + @Query(""" + SELECT c FROM Comment c + JOIN FETCH c.author + WHERE c.item.id IN :itemIds + """) + Collection findAllByItemIds(List itemIds); } 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 89c7b78..8b62cc0 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -2,16 +2,16 @@ import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemDtoWithBookings; +import ru.practicum.shareit.item.dto.ItemDtoWithBookingsAndComments; import ru.practicum.shareit.item.dto.UpdateItemDto; import java.util.Collection; public interface ItemService { - Collection getAllByUser(Long userId); + Collection getAllByUser(Long userId); - ItemDto getById(Long userId, Long id); + ItemDtoWithBookingsAndComments getById(Long userId, Long id); ItemDto create(Long userId, ItemDto item); 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 e951c17..840da1c 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -10,7 +10,7 @@ import ru.practicum.shareit.exception.ValidationException; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemDtoWithBookings; +import ru.practicum.shareit.item.dto.ItemDtoWithBookingsAndComments; import ru.practicum.shareit.item.dto.UpdateItemDto; import ru.practicum.shareit.item.mapper.CommentMapper; import ru.practicum.shareit.item.mapper.ItemMapper; @@ -38,7 +38,7 @@ public class ItemServiceImpl implements ItemService { private final CommentRepository commentRepository; @Override - public Collection getAllByUser(Long userId) { + public Collection getAllByUser(Long userId) { throwIfUserNotExist(userId); Collection items = itemRepository.findAllByOwnerId(userId); if (items.isEmpty()) { @@ -50,19 +50,29 @@ public Collection getAllByUser(Long userId) { Map> bookingsByItem = bookings.stream() .collect(Collectors.groupingBy(booking -> booking.getItem().getId())); + Collection comments = commentRepository.findAllByItemIds(itemsIds); + Map> commentsByItem = comments.stream() + .collect(Collectors.groupingBy(comment -> comment.getItem().getId())); + return items.stream() - .map(item -> ItemMapper.toItemDtoWithBookings(item, - bookingsByItem.getOrDefault(item.getId(), List.of()))) + .map(item -> ItemMapper.toItemDtoWithBookingsAndComments(item, + bookingsByItem.getOrDefault(item.getId(), List.of()), + commentsByItem.getOrDefault(item.getId(), List.of()))) .toList(); } @Override - public ItemDto getById(Long userId, Long itemId) { + public ItemDtoWithBookingsAndComments getById(Long userId, Long itemId) { throwIfUserNotExist(userId); - + Item item = throwIfItemNotExist(itemId); + Collection comments = commentRepository.findAllByItemId(itemId); + // если юзер не является собственников, не передаем информацию о бронированиях + List bookings = item.getOwner().getId().equals(userId) + ? bookingRepository.findAllByItemId(itemId) + : List.of(); return itemRepository.findById(itemId) - .map(ItemMapper::toItemDto) - .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найдена".formatted(itemId))); + .map(i -> ItemMapper.toItemDtoWithBookingsAndComments(i, bookings, comments)) + .orElseThrow(() -> new NotFoundException("Предмет с ID: %s не найден".formatted(itemId))); } @Override diff --git a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java index 8239e03..116e5d6 100644 --- a/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java +++ b/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -20,7 +20,6 @@ public ItemRequest toItemRequest(ItemRequestDto itemRequestDto) { return ItemRequest.builder() .id(itemRequestDto.getId()) .description(itemRequestDto.getDescription()) -// .requesterId(itemRequestDto.getId()) .created(itemRequestDto.getCreated()) .build(); } diff --git a/src/main/java/ru/practicum/shareit/util/Constants.java b/src/main/java/ru/practicum/shareit/util/Constants.java index 7793f44..5e4ed49 100644 --- a/src/main/java/ru/practicum/shareit/util/Constants.java +++ b/src/main/java/ru/practicum/shareit/util/Constants.java @@ -1,10 +1,7 @@ package ru.practicum.shareit.util; -import java.time.LocalDateTime; - public class Constants { public static final String USER_ID_HEADER = "X-Sharer-User-Id"; - public static final LocalDateTime NOW = LocalDateTime.now(); } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index ec40e52..973de9f 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,8 +1,8 @@ ---DROP TABLE IF EXISTS comments; ---DROP TABLE IF EXISTS bookings; ---DROP TABLE IF EXISTS items; ---DROP TABLE IF EXISTS requests; ---DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS comments; +DROP TABLE IF EXISTS bookings; +DROP TABLE IF EXISTS items; +DROP TABLE IF EXISTS requests; +DROP TABLE IF EXISTS users; CREATE TABLE IF NOT EXISTS users ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, From 54272148603adf10f98a1f0e9d9e53ed0f5561ef Mon Sep 17 00:00:00 2001 From: Egor Ilyin Date: Sun, 23 Nov 2025 13:13:12 +0300 Subject: [PATCH 9/9] fix test init --- src/main/resources/application-test.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index ac7ec0b..a7145cc 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -10,4 +10,3 @@ spring.datasource.url=jdbc:h2:mem:./db/shareit spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password -spring.sql.init.data-locations=classpath:data.sql