diff --git a/docker-compose.yaml b/docker-compose.yaml index 9f3dd90..577365c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,16 +1,42 @@ +version: '3.8' + +volumes: + postgres_data: + services: db: image: postgres:16.1 ports: - "5432:5432" - volumes: - - ./volumes/postgres:/var/lib/postgresql/data/ environment: - - POSTGRES_DB=shareit - - POSTGRES_USER=dbuser - - POSTGRES_PASSWORD=12345 + POSTGRES_USER: dbuser + POSTGRES_PASSWORD: 12345 + POSTGRES_DB: shareit + volumes: + - postgres_data:/var/lib/postgresql/data healthcheck: - test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER - timeout: 5s - interval: 5s - retries: 10 \ No newline at end of file + test: ["CMD-SHELL", "pg_isready -U dbuser -d shareit"] + + server: + build: + context: . + dockerfile: server/Dockerfile + ports: + - "9090:9090" + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit # ← db вместо postgres + - SPRING_DATASOURCE_USERNAME=dbuser + - SPRING_DATASOURCE_PASSWORD=12345 + depends_on: + - db # ← зависит от db, а не postgres + + gateway: + build: + context: . + dockerfile: gateway/Dockerfile + ports: + - "8080:8080" + environment: + - SHAREIT_SERVER_URL=http://server:9090 + depends_on: + - server \ No newline at end of file diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..5a8e692 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=gateway/target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..ef48862 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,70 @@ + + + + ru.practicum + shareit + 0.0.1-SNAPSHOT + ../pom.xml + + 4.0.0 + gateway + jar + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + + + org.projectlombok + lombok + provided + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.3.1 + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + ru.practicum.gateway.ShareItAppGateWay + + + + + diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/gateway/src/main/java/ru/practicum/gateway/ShareItAppGateWay.java similarity index 61% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to gateway/src/main/java/ru/practicum/gateway/ShareItAppGateWay.java index 83b60e0..9aa34a1 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/gateway/src/main/java/ru/practicum/gateway/ShareItAppGateWay.java @@ -1,11 +1,11 @@ -package ru.practicum.shareit; +package ru.practicum.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ShareItApp { +public class ShareItAppGateWay { public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); + SpringApplication.run(ShareItAppGateWay.class, args); } } diff --git a/gateway/src/main/java/ru/practicum/gateway/base/BaseClient.java b/gateway/src/main/java/ru/practicum/gateway/base/BaseClient.java new file mode 100644 index 0000000..d821430 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/base/BaseClient.java @@ -0,0 +1,126 @@ +package ru.practicum.gateway.base; + +import org.springframework.http.*; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +public class BaseClient { + protected final RestTemplate rest; + + public BaseClient(RestTemplate rest) { + this.rest = rest; + } + + protected ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + protected ResponseEntity get(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return put(path, userId, null, body); + } + + protected ResponseEntity put(String path, long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId) { + return patch(path, userId, null, null); + } + + protected ResponseEntity patch(String path, long userId, T body) { + return patch(path, userId, null, body); + } + + protected ResponseEntity patch(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + } + + protected ResponseEntity delete(String path) { + return delete(path, null, null); + } + + protected ResponseEntity delete(String path, long userId) { + return delete(path, userId, null); + } + + protected ResponseEntity delete(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + } + + protected ResponseEntity getWithHeaders(String path, long userId) { + HttpHeaders headers = new HttpHeaders(); + headers.set("X-Sharer-User-Id", String.valueOf(userId)); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + HttpEntity requestEntity = new HttpEntity<>(headers); + return rest.exchange(path, HttpMethod.GET, requestEntity, Object.class); + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, Long userId, @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); + + ResponseEntity shareitServerResponse; + try { + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareGatewayResponse(shareitServerResponse); + } + + private HttpHeaders defaultHeaders(Long userId) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + if (userId != null) { + headers.set("X-Sharer-User-Id", String.valueOf(userId)); + } + return headers; + } + + private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + + return responseBuilder.build(); + } +} diff --git a/gateway/src/main/java/ru/practicum/gateway/config/PatchMethodConfig.java b/gateway/src/main/java/ru/practicum/gateway/config/PatchMethodConfig.java new file mode 100644 index 0000000..db10efd --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/config/PatchMethodConfig.java @@ -0,0 +1,20 @@ +package ru.practicum.gateway.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.HiddenHttpMethodFilter; + +import java.util.List; + +@Configuration +public class PatchMethodConfig { + + @Bean + public FilterRegistrationBean hiddenHttpMethodFilter() { + FilterRegistrationBean filterRegistrationBean = + new FilterRegistrationBean<>(new HiddenHttpMethodFilter()); + filterRegistrationBean.setUrlPatterns(List.of("/*")); + return filterRegistrationBean; + } +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingClient.java b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingClient.java new file mode 100644 index 0000000..a1c5ce1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingClient.java @@ -0,0 +1,70 @@ +package ru.practicum.gateway.entity.booking; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.gateway.base.BaseClient; + +import java.util.Map; + +@Service +public class BookingClient extends BaseClient { + + private static final String API_PREFIX = "/bookings"; + + @Autowired + public BookingClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + +// @CacheEvict(value = "bookings", allEntries = true) + public ResponseEntity createBooking(BookingRequestDto bookingRequestDto, Long userId) { + return post("", userId, bookingRequestDto); + } + +// @CacheEvict(value = "bookings", key = "#bookingId") + public ResponseEntity approveBooking(Long bookingId, Boolean approved, Long userId) { + Map parameters = Map.of("approved", approved); + return patch("/" + bookingId + "?approved={approved}", userId, parameters, null); + } + +// @Cacheable(value = "bookings", key = "#bookingId") + public ResponseEntity getBookingById(Long bookingId, Long userId) { + return get("/" + bookingId, userId); + } + +// @Cacheable(value = "bookings", key = "'user_' + #userId + '_state_' + #state + '_from_' + #from + '_size_' + #size") + public ResponseEntity getUserBookings(Long userId, Status state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("?state={state}&from={from}&size={size}", userId, parameters); + } + +// @Cacheable(value = "bookings", key = "'user_' + #ownerId + '_state_' + #state + '_from_' + #from + '_size_' + #size") + public ResponseEntity getOwnerBookings(Long ownerId, Status state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("/owner?state={state}&from={from}&size={size}", ownerId, parameters); + } + +// @CacheEvict(value = "bookings", key = "#bookingId") + public ResponseEntity deleteBooking(long userId, Long bookingId) { + return delete("/" + bookingId, userId); + } + +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingController.java b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingController.java new file mode 100644 index 0000000..334843d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingController.java @@ -0,0 +1,69 @@ +package ru.practicum.gateway.entity.booking; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/bookings") +public class BookingController { + + private final BookingClient bookingClient; + + @PostMapping + public ResponseEntity createBooking( + @RequestHeader("X-Sharer-User-Id") Long userId, + @Valid @RequestBody BookingRequestDto bookingRequestDto) { + log.info("Creating booking {}, userId={}", bookingRequestDto, userId); + return bookingClient.createBooking(bookingRequestDto, userId); + } + + @PatchMapping("/{bookingId}") + public ResponseEntity approveBooking( + @RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long bookingId, + @RequestParam Boolean approved) { + return bookingClient.approveBooking(bookingId, approved, userId); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getBookingById( + @RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long bookingId) { + return bookingClient.getBookingById(bookingId, userId); + } + + @GetMapping + public ResponseEntity getUserBookings( + @RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "ALL") String state, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + Status status = Status.from(state) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + state)); + return bookingClient.getUserBookings(userId, status, from, size); + } + + @GetMapping("/owner") + public ResponseEntity getOwnerBookings( + @RequestHeader("X-Sharer-User-Id") Long ownerId, + @RequestParam(defaultValue = "ALL") String state, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + Status status = Status.from(state) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + state)); + return bookingClient.getOwnerBookings(ownerId, status, from, size); + } + + @DeleteMapping("/{bookingId}") + public ResponseEntity deleteBooking(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable Long bookingId) { + log.info("Delete booking {}, userId={}", bookingId, userId); + return bookingClient.deleteBooking(userId, bookingId); + } + +} diff --git a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingDto.java similarity index 87% rename from src/main/java/ru/practicum/shareit/entity/booking/dto/BookingDto.java rename to gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingDto.java index de12e06..d8e36da 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingDto.java +++ b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingDto.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.entity.booking.dto; +package ru.practicum.gateway.entity.booking; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingRequestDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingRequestDto.java similarity index 93% rename from src/main/java/ru/practicum/shareit/entity/booking/dto/BookingRequestDto.java rename to gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingRequestDto.java index 680d628..b6f345a 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingRequestDto.java +++ b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingRequestDto.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.entity.booking.dto; +package ru.practicum.gateway.entity.booking; import jakarta.validation.constraints.Future; import jakarta.validation.constraints.FutureOrPresent; @@ -14,6 +14,7 @@ @NoArgsConstructor @AllArgsConstructor public class BookingRequestDto { + private Long id; @NotNull(message = "Дата начала не может быть пустой") @FutureOrPresent(message = "Дата начала должна быть в будущем или настоящем") private LocalDateTime start; diff --git a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingResponseDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingResponseDto.java similarity index 62% rename from src/main/java/ru/practicum/shareit/entity/booking/dto/BookingResponseDto.java rename to gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingResponseDto.java index b6dda2b..e988d7d 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingResponseDto.java +++ b/gateway/src/main/java/ru/practicum/gateway/entity/booking/BookingResponseDto.java @@ -1,11 +1,11 @@ -package ru.practicum.shareit.entity.booking.dto; +package ru.practicum.gateway.entity.booking; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.shareit.entity.booking.enums.Status; -import ru.practicum.shareit.entity.item.model.dto.ItemDto; -import ru.practicum.shareit.entity.user.model.dto.UserDto; +import ru.practicum.gateway.entity.item.ItemDto; +import ru.practicum.gateway.entity.user.UserDto; + import java.time.LocalDateTime; diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/booking/Status.java b/gateway/src/main/java/ru/practicum/gateway/entity/booking/Status.java new file mode 100644 index 0000000..54944cf --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/booking/Status.java @@ -0,0 +1,39 @@ +package ru.practicum.gateway.entity.booking; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Optional; + +@Getter +@AllArgsConstructor +public enum Status { + ALL("ALL"), + CURRENT("CURRENT"), + WAITING("WAITING"), + FUTURE("FUTURE"), + PAST("PAST"), + APPROVED("APPROVED"), + REJECTED("REJECTED"), + CANCELED("CANCELED"); + + private final String status; + + public static boolean isCorrectStatus(String testedValue) { + for (Status status : values()) { + if (status.status.equals(testedValue)) { + return true; + } + } + return false; + } + + public static Optional from(String stringState) { + for (Status state : values()) { + if (state.name().equalsIgnoreCase(stringState)) { + return Optional.of(state); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/ru/practicum/shareit/entity/comment/model/CommentDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/item/CommentDto.java similarity index 85% rename from src/main/java/ru/practicum/shareit/entity/comment/model/CommentDto.java rename to gateway/src/main/java/ru/practicum/gateway/entity/item/CommentDto.java index df05a20..a3ff8e3 100644 --- a/src/main/java/ru/practicum/shareit/entity/comment/model/CommentDto.java +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item/CommentDto.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.entity.comment.model; +package ru.practicum.gateway.entity.item; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemClient.java b/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemClient.java new file mode 100644 index 0000000..e862522 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemClient.java @@ -0,0 +1,88 @@ +package ru.practicum.gateway.entity.item; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; + +import org.springframework.http.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.gateway.base.BaseClient; + +import java.util.Map; + +@Slf4j +@Service +public class ItemClient extends BaseClient { + + private static final String API_PREFIX = "/items"; + + public ItemClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + +// @Cacheable(value = "items", key = "#itemId") + public ResponseEntity getItemById(Long itemId) { + log.info("Отправка запроса на получение информации о вещи с ID={}", itemId); + return get("/" + itemId); + } + +// @Cacheable(value = "items", key = "'user_' + #userId") + public ResponseEntity getItemByUserId(Long userId) { + log.info("Отправка запроса на получение списка вещей пользователя с ID={}", userId); + return get("", userId); + } + +// @Cacheable(value = "items", key = "'search_' + #text") + public ResponseEntity searchText(String text) { + log.info("Отправка запроса на поиск вещей по тексту: {}", text); + if (text == null || text.isBlank()) { + return get("/search"); + } + return get("/search?text=" + text); + } + +// @CacheEvict(value = "items", allEntries = true) + public ResponseEntity create(Long userId, ItemDto itemDto) { + log.info("Отправка запроса на создание новой вещи для пользователя с ID={}, данные: {}", userId, itemDto); + return post("", userId, itemDto); + } + +// @CacheEvict(value = "items", key = "#itemId") + public ResponseEntity update(Long itemId, Long userId, Map updates) { + log.info("Отправка запроса на обновление вещи с ID={} для пользователя с ID={}, данные: {}", itemId, userId, updates); + return patch("/" + itemId, userId, updates); + } + +// @CacheEvict(value = "items", key = "#itemId") + public ResponseEntity addComment(Long itemId, Long userId, CommentDto commentDto) { + log.info("Отправка запроса на добавление комментария к вещи с ID={} от пользователя с ID={}, текст: {}", + itemId, userId, commentDto.getText()); + return post("/" + itemId + "/comment", userId, commentDto); + + } + +// @CacheEvict(value = "items", key = "#itemId") + public ResponseEntity deleteItem(Long itemId) { + return delete("/" + itemId); + } + +// @Cacheable(value = "items", key = "'item_' + #itemId + '_user_' + #userId") + public ResponseEntity getItemDtoWithBookingsAndComments(Long itemId, Long userId) { + log.info("Запрос информации о вещи с ID={} для пользователя {}", itemId, userId); + log.debug("Полный URL: {}/{}", rest.getUriTemplateHandler().toString(), itemId); + + ResponseEntity response = getWithHeaders("/" + itemId, userId); + + log.debug("Ответ от сервера: статус {}, тело: {}", + response.getStatusCode(), response.getBody()); + + return response; + } +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemController.java b/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemController.java new file mode 100644 index 0000000..39c945a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemController.java @@ -0,0 +1,61 @@ +package ru.practicum.gateway.entity.item; + +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/items") +public class ItemController { + + private final ItemClient itemClient; + + public ItemController(ItemClient itemClient) { + this.itemClient = itemClient; + } + + @GetMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getItemById(@PathVariable Long itemId, + @RequestHeader("X-Sharer-User-Id") Long userId) { + return itemClient.getItemDtoWithBookingsAndComments(itemId, userId); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getItemByUserId(@RequestHeader("X-Sharer-User-Id") Long userId) { + return itemClient.getItemByUserId(userId); + } + + @GetMapping("/search") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity searchText(@RequestParam("text") String text) { + return itemClient.searchText(text); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity create(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody @Valid ItemDto itemDto) { + return itemClient.create(userId, itemDto); + } + + @PatchMapping("/{itemId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity update(@PathVariable Long itemId, + @RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody Map updates) { + return itemClient.update(itemId, userId, updates); + } + + @PostMapping("/{itemId}/comment") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity addComment(@PathVariable Long itemId, + @RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody CommentDto commentDto) { + return itemClient.addComment(itemId, userId, commentDto); + } +} diff --git a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemDto.java similarity index 80% rename from src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemDto.java rename to gateway/src/main/java/ru/practicum/gateway/entity/item/ItemDto.java index 9f5043a..1c85650 100644 --- a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemDto.java +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item/ItemDto.java @@ -1,12 +1,13 @@ -package ru.practicum.shareit.entity.item.model.dto; +package ru.practicum.gateway.entity.item; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.shareit.entity.booking.dto.BookingDto; -import ru.practicum.shareit.entity.comment.model.CommentDto; +import ru.practicum.gateway.entity.booking.BookingDto; + import java.util.List; @@ -30,4 +31,7 @@ public class ItemDto { private BookingDto nextBooking; private List comments; + + @Nullable + private Long requestId; } diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestClient.java new file mode 100644 index 0000000..280ccb7 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestClient.java @@ -0,0 +1,59 @@ +package ru.practicum.gateway.entity.item_request; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.gateway.base.BaseClient; + +import java.util.Map; + +@Slf4j +@Service +public class ItemRequestClient extends BaseClient { + + private static final String API_PREFIX = "/requests"; + + @Autowired + public ItemRequestClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + +// @Cacheable(value = "itemRequests", key = "#requestId") + public ResponseEntity getRequestById(Long userId, Long requestId) { + log.info("Отправка запроса на получение информации о запросе вещи с ID={}", requestId); + return get("/" + requestId, userId); + } + + +// @Cacheable(value = "itemRequests", key = "'user_' + #userId") + public ResponseEntity getUserRequests(Long userId) { + log.info("Отправка запроса на получение списка запросов вещей пользователя с ID={}", userId); + return get("", userId); + } + +// @Cacheable(value = "itemRequests", key = "'all_user_' + #userId + '_from_' + #from + '_size_' + #size") + public ResponseEntity getAllRequests(Long userId, Integer from, Integer size) { + log.info("Отправка запроса на получение всех запросов вещей, кроме пользователя с ID={}, начиная с {} по {}", userId, from, size); + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("/all", userId, parameters); + } + +// @CacheEvict(value = "itemRequests", allEntries = true) + public ResponseEntity createRequest(Long userId, ItemRequestDto itemRequestDto) { + log.info("Отправка запроса на создание нового запроса вещи от пользователя с ID={}, данные: {}", userId, itemRequestDto); + return post("", userId, itemRequestDto); + } +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestController.java b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestController.java new file mode 100644 index 0000000..a203a96 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestController.java @@ -0,0 +1,45 @@ +package ru.practicum.gateway.entity.item_request; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping(path = "/requests") +public class ItemRequestController { + + private final ItemRequestClient itemRequestClient; + + @GetMapping("/{requestId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long requestId) { + return itemRequestClient.getRequestById(userId, requestId); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + return itemRequestClient.getUserRequests(userId); + } + + @GetMapping("/all") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getAllRequests( + @RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + return itemRequestClient.getAllRequests(userId, from, size); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createRequest( + @RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody @Valid ItemRequestDto itemRequestDto) { + return itemRequestClient.createRequest(userId, itemRequestDto); + } +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestDto.java new file mode 100644 index 0000000..a9f85a4 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemRequestDto.java @@ -0,0 +1,16 @@ +package ru.practicum.gateway.entity.item_request; + +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +public class ItemRequestDto { + private long id; + private String description; + private LocalDateTime created; + private List items; +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemResponseDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemResponseDto.java new file mode 100644 index 0000000..629bcd9 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/item_request/ItemResponseDto.java @@ -0,0 +1,14 @@ +package ru.practicum.gateway.entity.item_request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class ItemResponseDto { + private Long itemId; + private String name; + private Long ownerId; +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/user/UserClient.java b/gateway/src/main/java/ru/practicum/gateway/entity/user/UserClient.java new file mode 100644 index 0000000..f020b1d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/user/UserClient.java @@ -0,0 +1,63 @@ +package ru.practicum.gateway.entity.user; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.gateway.base.BaseClient; + +import java.util.Map; + +@Slf4j +@Service +public class UserClient extends BaseClient { + + private static final String API_PREFIX = "/users"; + + @Autowired + public UserClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + +// @Cacheable(value = "users", key = "'all'") + public ResponseEntity getUserList() { + log.info("Отправка запроса на получение списка всех пользователей"); + ResponseEntity response = get(""); + log.info("Получен список пользователей"); + return response; + } + +// @Cacheable(value = "users", key = "#id") + public ResponseEntity getUserDto(Long id) { + log.info("Отправка запроса на получение пользователя с id={}", id); + return get("/" + id); + } + +// @CacheEvict(value = "users", allEntries = true) + public ResponseEntity create(UserDto userDto) { + log.info("Отправка запроса на создание пользователя: {}", userDto); + return post("", userDto); + } + +// @CacheEvict(value = "users", key = "#userId") + public ResponseEntity update(Long userId, Map updates) { + log.info("Отправка запроса на обновление пользователя с ID={}, данные: {}", userId, updates); + return patch("/" + userId, updates); + } + + +// @CacheEvict(value = "users", key = "#id") + public ResponseEntity delete(Long id) { + log.info("Отправка запроса на удаление пользователя с id={}", id); + return delete("/" + id); + } +} diff --git a/gateway/src/main/java/ru/practicum/gateway/entity/user/UserController.java b/gateway/src/main/java/ru/practicum/gateway/entity/user/UserController.java new file mode 100644 index 0000000..d1b6513 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/gateway/entity/user/UserController.java @@ -0,0 +1,50 @@ +package ru.practicum.gateway.entity.user; + +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping(path = "/users") +public class UserController { + + private final UserClient userClient; + + public UserController(UserClient userClient) { + this.userClient = userClient; + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getUserDto(@PathVariable Long id) { + return userClient.getUserDto(id); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getUserList() { + return userClient.getUserList(); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity create(@RequestBody @Valid UserDto user) { + return userClient.create(user); + } + + @PatchMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity update(@PathVariable Long id, + @RequestBody @Valid Map updates) { + return userClient.update(id, updates); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public ResponseEntity delete(@PathVariable Long id) { + return userClient.delete(id); + } +} diff --git a/src/main/java/ru/practicum/shareit/entity/user/model/dto/UserDto.java b/gateway/src/main/java/ru/practicum/gateway/entity/user/UserDto.java similarity index 88% rename from src/main/java/ru/practicum/shareit/entity/user/model/dto/UserDto.java rename to gateway/src/main/java/ru/practicum/gateway/entity/user/UserDto.java index 68768b1..fadae95 100644 --- a/src/main/java/ru/practicum/shareit/entity/user/model/dto/UserDto.java +++ b/gateway/src/main/java/ru/practicum/gateway/entity/user/UserDto.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.entity.user.model.dto; +package ru.practicum.gateway.entity.user; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/ru/practicum/shareit/exception/ConflictException.java b/gateway/src/main/java/ru/practicum/gateway/exception/ConflictException.java similarity index 77% rename from src/main/java/ru/practicum/shareit/exception/ConflictException.java rename to gateway/src/main/java/ru/practicum/gateway/exception/ConflictException.java index 7885123..15ea7be 100644 --- a/src/main/java/ru/practicum/shareit/exception/ConflictException.java +++ b/gateway/src/main/java/ru/practicum/gateway/exception/ConflictException.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.gateway.exception; public class ConflictException extends RuntimeException { public ConflictException(String message) { diff --git a/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java b/gateway/src/main/java/ru/practicum/gateway/exception/ForbiddenException.java similarity index 77% rename from src/main/java/ru/practicum/shareit/exception/ForbiddenException.java rename to gateway/src/main/java/ru/practicum/gateway/exception/ForbiddenException.java index 9266153..43c9e2f 100644 --- a/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java +++ b/gateway/src/main/java/ru/practicum/gateway/exception/ForbiddenException.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.gateway.exception; public class ForbiddenException extends RuntimeException { public ForbiddenException(String message) { diff --git a/src/main/java/ru/practicum/shareit/exception/InternalServerException.java b/gateway/src/main/java/ru/practicum/gateway/exception/InternalServerException.java similarity index 78% rename from src/main/java/ru/practicum/shareit/exception/InternalServerException.java rename to gateway/src/main/java/ru/practicum/gateway/exception/InternalServerException.java index 8d52fd7..a5d40fd 100644 --- a/src/main/java/ru/practicum/shareit/exception/InternalServerException.java +++ b/gateway/src/main/java/ru/practicum/gateway/exception/InternalServerException.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.gateway.exception; public class InternalServerException extends RuntimeException { public InternalServerException(String message) { diff --git a/src/main/java/ru/practicum/shareit/exception/NotFoundException.java b/gateway/src/main/java/ru/practicum/gateway/exception/NotFoundException.java similarity index 77% rename from src/main/java/ru/practicum/shareit/exception/NotFoundException.java rename to gateway/src/main/java/ru/practicum/gateway/exception/NotFoundException.java index 508b545..76828a4 100644 --- a/src/main/java/ru/practicum/shareit/exception/NotFoundException.java +++ b/gateway/src/main/java/ru/practicum/gateway/exception/NotFoundException.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.gateway.exception; public class NotFoundException extends RuntimeException { public NotFoundException(String message) { diff --git a/src/main/java/ru/practicum/shareit/exception/ValidationException.java b/gateway/src/main/java/ru/practicum/gateway/exception/ValidationException.java similarity index 78% rename from src/main/java/ru/practicum/shareit/exception/ValidationException.java rename to gateway/src/main/java/ru/practicum/gateway/exception/ValidationException.java index 59043da..7b256df 100644 --- a/src/main/java/ru/practicum/shareit/exception/ValidationException.java +++ b/gateway/src/main/java/ru/practicum/gateway/exception/ValidationException.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception; +package ru.practicum.gateway.exception; public class ValidationException extends RuntimeException { public ValidationException(String message) { diff --git a/src/main/java/ru/practicum/shareit/exception/handling/ErrorHandler.java b/gateway/src/main/java/ru/practicum/gateway/exception/handling/ErrorHandler.java similarity index 98% rename from src/main/java/ru/practicum/shareit/exception/handling/ErrorHandler.java rename to gateway/src/main/java/ru/practicum/gateway/exception/handling/ErrorHandler.java index 7df8ac9..c270b9f 100644 --- a/src/main/java/ru/practicum/shareit/exception/handling/ErrorHandler.java +++ b/gateway/src/main/java/ru/practicum/gateway/exception/handling/ErrorHandler.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception.handling; +package ru.practicum.gateway.exception.handling; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import ru.practicum.shareit.exception.*; +import ru.practicum.gateway.exception.*; @Slf4j @RestControllerAdvice diff --git a/src/main/java/ru/practicum/shareit/exception/handling/ErrorResponse.java b/gateway/src/main/java/ru/practicum/gateway/exception/handling/ErrorResponse.java similarity index 57% rename from src/main/java/ru/practicum/shareit/exception/handling/ErrorResponse.java rename to gateway/src/main/java/ru/practicum/gateway/exception/handling/ErrorResponse.java index 472fc25..f9c7f4b 100644 --- a/src/main/java/ru/practicum/shareit/exception/handling/ErrorResponse.java +++ b/gateway/src/main/java/ru/practicum/gateway/exception/handling/ErrorResponse.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.exception.handling; +package ru.practicum.gateway.exception.handling; public record ErrorResponse(String error, String description) { } diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..0f9c0cc --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,4 @@ +server.port=8080 + +shareit-server.url=http://localhost:9090 +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml index f968403..140c213 100644 --- a/pom.xml +++ b/pom.xml @@ -15,75 +15,91 @@ ru.practicum shareit 0.0.1-SNAPSHOT + pom - ShareIt + ShareIt + + gateway + server + - + 21 + ${java.version} + ${java.version} + 1.5.5.Final + 0.2.0 + 1.18.32 - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.postgresql - postgresql - runtime - 42.7.3 - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.projectlombok - lombok - true - - - - com.h2database - h2 - test - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-validation - - - - - - - src/main/resources - true - - + + + + + org.postgresql + postgresql + 42.7.3 + runtime + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + + org.apache.maven.plugins maven-checkstyle-plugin @@ -226,6 +242,7 @@ + @@ -250,5 +267,4 @@ - diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..ad19173 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=server/target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..a121c02 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,74 @@ + + + + ru.practicum + shareit + 0.0.1-SNAPSHOT + ../pom.xml + + + 4.0.0 + server + jar + + + 9090 + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.postgresql + postgresql + runtime + + + + + org.projectlombok + lombok + provided + + + + + org.mapstruct + mapstruct + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + ru.practicum.server.shareit.ShareItAppServer + + + + + \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/server/shareit/ShareItAppServer.java b/server/src/main/java/ru/practicum/server/shareit/ShareItAppServer.java new file mode 100644 index 0000000..93fa4d4 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/ShareItAppServer.java @@ -0,0 +1,11 @@ +package ru.practicum.server.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItAppServer { + public static void main(String[] args) { + SpringApplication.run(ShareItAppServer.class, args); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/Validator.java b/server/src/main/java/ru/practicum/server/shareit/Validator.java new file mode 100644 index 0000000..a6e5057 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/Validator.java @@ -0,0 +1,4 @@ +package ru.practicum.server.shareit; + +public class Validator { +} diff --git a/server/src/main/java/ru/practicum/server/shareit/config/PatchMethodConfig.java b/server/src/main/java/ru/practicum/server/shareit/config/PatchMethodConfig.java new file mode 100644 index 0000000..06c2834 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/config/PatchMethodConfig.java @@ -0,0 +1,20 @@ +package ru.practicum.server.shareit.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.HiddenHttpMethodFilter; + +import java.util.List; + +@Configuration +public class PatchMethodConfig { + + @Bean + public FilterRegistrationBean hiddenHttpMethodFilter() { + FilterRegistrationBean filterRegistrationBean = + new FilterRegistrationBean<>(new HiddenHttpMethodFilter()); + filterRegistrationBean.setUrlPatterns(List.of("/*")); + return filterRegistrationBean; + } +} diff --git a/src/main/java/ru/practicum/shareit/entity/booking/Booking.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/Booking.java similarity index 78% rename from src/main/java/ru/practicum/shareit/entity/booking/Booking.java rename to server/src/main/java/ru/practicum/server/shareit/entity/booking/Booking.java index 193dc3d..4ef7b4a 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/Booking.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/Booking.java @@ -1,13 +1,13 @@ -package ru.practicum.shareit.entity.booking; +package ru.practicum.server.shareit.entity.booking; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import ru.practicum.shareit.entity.booking.enums.Status; -import ru.practicum.shareit.entity.item.model.Item; -import ru.practicum.shareit.entity.user.model.User; +import ru.practicum.server.shareit.entity.booking.enums.Status; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.user.model.User; import java.time.LocalDateTime; diff --git a/src/main/java/ru/practicum/shareit/entity/booking/BookingController.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingController.java similarity index 56% rename from src/main/java/ru/practicum/shareit/entity/booking/BookingController.java rename to server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingController.java index 396e439..c510fea 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingController.java @@ -1,11 +1,11 @@ -package ru.practicum.shareit.entity.booking; +package ru.practicum.server.shareit.entity.booking; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.entity.booking.dto.BookingRequestDto; -import ru.practicum.shareit.entity.booking.dto.BookingResponseDto; +import ru.practicum.server.shareit.entity.booking.dto.BookingRequestDto; +import ru.practicum.server.shareit.entity.booking.dto.BookingResponseDto; import java.util.List; @@ -17,42 +17,47 @@ public class BookingController { private final BookingService bookingService; @PostMapping - public ResponseEntity createBooking( + @ResponseStatus(HttpStatus.OK) + public BookingResponseDto createBooking( @RequestHeader("X-Sharer-User-Id") Long userId, @Valid @RequestBody BookingRequestDto bookingRequestDto) { - return ResponseEntity.ok(bookingService.createBooking(bookingRequestDto, userId)); + return bookingService.createBooking(bookingRequestDto, userId); } @PatchMapping("/{bookingId}") - public ResponseEntity approveBooking( + @ResponseStatus(HttpStatus.OK) + public BookingResponseDto approveBooking( @RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long bookingId, @RequestParam Boolean approved) { - return ResponseEntity.ok(bookingService.approveBooking(bookingId, approved, userId)); + return bookingService.approveBooking(bookingId, approved, userId); } @GetMapping("/{bookingId}") - public ResponseEntity getBookingById( + @ResponseStatus(HttpStatus.OK) + public BookingResponseDto getBookingById( @RequestHeader("X-Sharer-User-Id") Long userId, @PathVariable Long bookingId) { - return ResponseEntity.ok(bookingService.getBookingById(bookingId, userId)); + return bookingService.getBookingById(bookingId, userId); } @GetMapping - public ResponseEntity> getUserBookings( + @ResponseStatus(HttpStatus.OK) + public List getUserBookings( @RequestHeader("X-Sharer-User-Id") Long userId, @RequestParam(defaultValue = "ALL") String state, @RequestParam(defaultValue = "0") Integer from, @RequestParam(defaultValue = "10") Integer size) { - return ResponseEntity.ok(bookingService.getUserBookings(userId, state, from, size)); + return bookingService.getUserBookings(userId, state, from, size); } @GetMapping("/owner") - public ResponseEntity> getOwnerBookings( + @ResponseStatus(HttpStatus.OK) + public List getOwnerBookings( @RequestHeader("X-Sharer-User-Id") Long ownerId, @RequestParam(defaultValue = "ALL") String state, @RequestParam(defaultValue = "0") Integer from, @RequestParam(defaultValue = "10") Integer size) { - return ResponseEntity.ok(bookingService.getOwnerBookings(ownerId, state, from, size)); + return bookingService.getOwnerBookings(ownerId, state, from, size); } } diff --git a/src/main/java/ru/practicum/shareit/entity/booking/BookingRepository.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingRepository.java similarity index 76% rename from src/main/java/ru/practicum/shareit/entity/booking/BookingRepository.java rename to server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingRepository.java index 470a54a..e1cb786 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/BookingRepository.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingRepository.java @@ -1,10 +1,11 @@ -package ru.practicum.shareit.entity.booking; +package ru.practicum.server.shareit.entity.booking; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import ru.practicum.shareit.entity.booking.enums.Status; +import ru.practicum.server.shareit.entity.booking.enums.Status; import java.time.LocalDateTime; import java.util.List; @@ -71,11 +72,24 @@ boolean existsApprovedBookingsForItemBetweenDates( @Query("SELECT b FROM Booking b WHERE " + "b.item.id = :itemId AND " + + "b.booker.id = :userId AND " + // Добавьте это условие "b.status = 'APPROVED' AND " + "b.end < :now " + "ORDER BY b.end DESC") List findLastBookingForItem( @Param("itemId") Long itemId, + @Param("userId") Long userId, // Добавьте этот параметр + @Param("now") LocalDateTime now, + Pageable pageable); + + @Query("SELECT b FROM Booking b WHERE " + + "b.item.id = :itemId AND " + + "b.booker.id = :userId AND " + + "b.end < :now " + // Убрали проверку статуса + "ORDER BY b.end DESC") + List findLastBookingForItemSimple( + @Param("itemId") Long itemId, + @Param("userId") Long userId, @Param("now") LocalDateTime now, Pageable pageable); @@ -94,4 +108,15 @@ boolean existsByBookerIdAndItemIdAndEndIsBefore( @Param("bookerId") Long bookerId, @Param("itemId") Long itemId, @Param("endDate") LocalDateTime endDate); + + @Modifying + @Query("DELETE FROM Booking b WHERE b.booker.id = :userId") + void deleteByBookerId(@Param("userId") Long userId); + + @Query(value = "SELECT b.* FROM bookings as b " + + "JOIN items as i ON i.id = b.item_id " + + "WHERE b.booker_id = ?1 " + + "AND i.id = ?2 " + + "AND b.status = 'APPROVED' ", nativeQuery = true) + List findAllByUserBookings(Long userId, Long itemId, LocalDateTime now); } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/entity/booking/BookingService.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingService.java similarity index 72% rename from src/main/java/ru/practicum/shareit/entity/booking/BookingService.java rename to server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingService.java index 5bbabfb..842ebac 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/BookingService.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingService.java @@ -1,7 +1,7 @@ -package ru.practicum.shareit.entity.booking; +package ru.practicum.server.shareit.entity.booking; -import ru.practicum.shareit.entity.booking.dto.BookingRequestDto; -import ru.practicum.shareit.entity.booking.dto.BookingResponseDto; +import ru.practicum.server.shareit.entity.booking.dto.BookingRequestDto; +import ru.practicum.server.shareit.entity.booking.dto.BookingResponseDto; import java.util.List; diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingServiceImpl.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingServiceImpl.java new file mode 100644 index 0000000..a71a9bc --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/BookingServiceImpl.java @@ -0,0 +1,200 @@ +package ru.practicum.server.shareit.entity.booking; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.server.shareit.entity.booking.dto.BookingMapper; +import ru.practicum.server.shareit.entity.booking.dto.BookingRequestDto; +import ru.practicum.server.shareit.entity.booking.dto.BookingResponseDto; +import ru.practicum.server.shareit.entity.booking.enums.Status; +import ru.practicum.server.shareit.exception.NotFoundException; +import ru.practicum.server.shareit.exception.ValidationException; +import ru.practicum.server.shareit.entity.item.services.ItemService; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.user.UserService; +import ru.practicum.server.shareit.entity.user.model.User; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class BookingServiceImpl implements BookingService { + private final BookingRepository bookingRepository; + private final UserService userService; + private final ItemService itemService; + private final BookingMapper bookingMapper; + + @Override + @Transactional + public BookingResponseDto createBooking(BookingRequestDto bookingRequestDto, Long userId) { + log.info("Создание нового бронирования для пользователя с ID={}, данные: {}", userId, bookingRequestDto); + User booker = userService.getUser(userId); + Item item = itemService.getItemById(bookingRequestDto.getItemId()); + validateBooking(bookingRequestDto, item, booker); + Booking booking = bookingMapper.toBooking(bookingRequestDto, item, booker); + booking.setStatus(Status.WAITING); + Booking savedBooking = bookingRepository.save(booking); + BookingResponseDto responseDto = bookingMapper.toResponseDto(savedBooking); + log.info("Создано новое бронирование с ID={}", savedBooking.getId()); + return responseDto; + } + + @Override + @Transactional + public BookingResponseDto approveBooking(Long bookingId, Boolean approved, Long userId) { + log.info("Подтверждение/отклонение бронирования с ID={} пользователем с ID={}, статус: {}", + bookingId, userId, approved ? "подтверждено" : "отклонено"); + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Бронирование не найдено")); + if (!booking.getItem().getOwner().getId().equals(userId)) { + log.error("Попытка подтверждения бронирования пользователем, который не является владельцем вещи"); + throw new ValidationException("Подтверждать бронирование может только владелец вещи"); + } + if (booking.getStatus() != Status.WAITING) { + log.error("Бронирование с ID={} уже было обработано", bookingId); + throw new ValidationException("Бронирование уже было обработано"); + } + booking.setStatus(approved ? Status.APPROVED : Status.REJECTED); + Booking updatedBooking = bookingRepository.save(booking); + BookingResponseDto responseDto = bookingMapper.toResponseDto(updatedBooking); + log.info("Бронирование с ID={} обновлено, новый статус: {}", bookingId, updatedBooking.getStatus()); + return responseDto; + } + + @Override + public BookingResponseDto getBookingById(Long bookingId, Long userId) { + log.info("Получение информации о бронировании с ID={} для пользователя с ID={}", bookingId, userId); + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Бронирование не найдено")); + if (!booking.getBooker().getId().equals(userId) && + !booking.getItem().getOwner().getId().equals(userId)) { + log.error("Попытка просмотра бронирования пользователем, который не является автором или владельцем"); + throw new ValidationException("Просмотр бронирования доступен только автору или владельцу"); + } + BookingResponseDto responseDto = bookingMapper.toResponseDto(booking); + log.info("Получено бронирование с ID={}", bookingId); + return responseDto; + } + + @Override + public List getUserBookings(Long userId, String state, Integer from, Integer size) { + log.info("Получение списка бронирований пользователя с ID={}, состояние: {}, с {} по {}", + userId, state, from, size); + userService.validateUserExists(userId); + Pageable pageable = PageRequest.of(from / size, size, Sort.by("start").descending()); + LocalDateTime now = LocalDateTime.now(); + List bookings; + switch (state.toUpperCase()) { + case "CURRENT": + bookings = bookingRepository.findCurrentByBookerId(userId, now, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "PAST": + bookings = bookingRepository.findPastByBookerId(userId, now, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "FUTURE": + bookings = bookingRepository.findFutureByBookerId(userId, now, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "WAITING": + case "REJECTED": + Status status = Status.valueOf(state.toUpperCase()); + bookings = bookingRepository.findByBookerIdAndStatusOrderByStartDesc(userId, status, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "ALL": + bookings = bookingRepository.findByBookerIdOrderByStartDesc(userId, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + default: + log.error("Неизвестное состояние бронирования: {}", state); + throw new ValidationException("Unknown state: " + state); + } + log.info("Найдено {} бронирований для пользователя с ID={}", bookings.size(), userId); + return bookings; + } + + @Override + public List getOwnerBookings(Long ownerId, String state, Integer from, Integer size) { + log.info("Получение списка бронирований для вещей владельца с ID={}, состояние: {}, с {} по {}", + ownerId, state, from, size); + userService.validateUserExists(ownerId); + Pageable pageable = PageRequest.of(from / size, size, Sort.by("start").descending()); + LocalDateTime now = LocalDateTime.now(); + List bookings; + switch (state.toUpperCase()) { + case "CURRENT": + bookings = bookingRepository.findCurrentByOwnerId(ownerId, now, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "PAST": + bookings = bookingRepository.findPastByOwnerId(ownerId, now, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "FUTURE": + bookings = bookingRepository.findFutureByOwnerId(ownerId, now, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "WAITING": + case "REJECTED": + Status status = Status.valueOf(state.toUpperCase()); + bookings = bookingRepository.findByItemOwnerIdAndStatusOrderByStartDesc(ownerId, status, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + case "ALL": + bookings = bookingRepository.findByItemOwnerIdOrderByStartDesc(ownerId, pageable).stream() + .map(bookingMapper::toResponseDto) + .collect(Collectors.toList()); + break; + default: + log.error("Неизвестное состояние бронирования: {}", state); + throw new ValidationException("Unknown state: " + state); + } + log.info("Найдено {} бронирований для вещей владельца с ID={}", bookings.size(), ownerId); + return bookings; + } + + private void validateBooking(BookingRequestDto bookingDto, Item item, User booker) { + log.info("Валидация данных для бронирования: {}", bookingDto); + if (userService.getUser(booker.getId()) == null) { + log.error("Пользователя с ID={} не существует", booker.getId()); + throw new NotFoundException("Пользователя с таким id не существует"); + } + if (itemService.getItemById(item.getId()) == null) { + log.error("Вещи с ID={} не существует", item.getId()); + throw new NotFoundException("Вещи с таким id не существует"); + } + if (item.getOwner().getId().equals(booker.getId())) { + log.error("Владелец вещи с ID={} пытается забронировать свою вещь", item.getId()); + throw new ValidationException("Владелец не может бронировать свою вещь"); + } + if (!item.getAvailable()) { + log.error("Вещь с ID={} недоступна для бронирования", item.getId()); + throw new ValidationException("Вещь недоступна для бронирования"); + } + if (bookingRepository.existsApprovedBookingsForItemBetweenDates( + item.getId(), bookingDto.getStart(), bookingDto.getEnd())) { + log.error("Вещь с ID={} уже забронирована на указанные даты", item.getId()); + throw new ValidationException("Вещь уже забронирована на указанные даты"); + } + log.info("Валидация данных для бронирования пройдена успешно"); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingDto.java new file mode 100644 index 0000000..1ac92ba --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingDto.java @@ -0,0 +1,19 @@ +package ru.practicum.server.shareit.entity.booking.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +public class BookingDto { + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private Long itemId; + private Long bookerId; + private String status; +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingMapper.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingMapper.java new file mode 100644 index 0000000..629ff0e --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingMapper.java @@ -0,0 +1,36 @@ +package ru.practicum.server.shareit.entity.booking.dto; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import ru.practicum.server.shareit.entity.booking.Booking; +import ru.practicum.server.shareit.entity.booking.enums.Status; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.item.dto.ItemMapper; +import ru.practicum.server.shareit.entity.user.model.User; +import ru.practicum.server.shareit.entity.user.model.dto.UserMapper; + +@Mapper(componentModel = "spring", + uses = {ItemMapper.class, UserMapper.class}, + injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface BookingMapper { + + @Mapping(source = "bookingRequestDto.id", target = "id") + Booking toBooking(BookingRequestDto bookingRequestDto, Item item, User booker); + + BookingResponseDto toResponseDto(Booking booking); + + @Mapping(source = "item.id", target = "itemId") + BookingRequestDto toRequestDto(Booking booking); + + @Mapping(source = "item.id", target = "itemId") + @Mapping(source = "booker.id", target = "bookerId") + @Mapping(source = "status", target = "status", qualifiedByName = "mapStatusToString") + BookingDto toBookingDto(Booking booking); + + @Named("mapStatusToString") + default String mapStatusToString(Status status) { + return status != null ? status.name() : null; + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingRequestDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingRequestDto.java new file mode 100644 index 0000000..f03900b --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingRequestDto.java @@ -0,0 +1,29 @@ +package ru.practicum.server.shareit.entity.booking.dto; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BookingRequestDto { + private Long id; + @NotNull(message = "Дата начала не может быть пустой") + @FutureOrPresent(message = "Дата начала должна быть в будущем или настоящем") + private LocalDateTime start; + + @NotNull(message = "Дата окончания не может быть пустой") + @Future(message = "Дата окончания должна быть в будущем") + private LocalDateTime end; + + @NotNull(message = "ID вещи обязательно") + @Positive(message = "ID вещи должен быть положительным") + private Long itemId; +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingResponseDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingResponseDto.java new file mode 100644 index 0000000..e9cc9f3 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/dto/BookingResponseDto.java @@ -0,0 +1,22 @@ +package ru.practicum.server.shareit.entity.booking.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.server.shareit.entity.booking.enums.Status; +import ru.practicum.server.shareit.entity.item.dto.ItemDto; +import ru.practicum.server.shareit.entity.user.model.dto.UserDto; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BookingResponseDto { + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private Status status; + private UserDto booker; + private ItemDto item; +} diff --git a/src/main/java/ru/practicum/shareit/entity/booking/enums/Status.java b/server/src/main/java/ru/practicum/server/shareit/entity/booking/enums/Status.java similarity index 89% rename from src/main/java/ru/practicum/shareit/entity/booking/enums/Status.java rename to server/src/main/java/ru/practicum/server/shareit/entity/booking/enums/Status.java index 05c3342..266f646 100644 --- a/src/main/java/ru/practicum/shareit/entity/booking/enums/Status.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/booking/enums/Status.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.entity.booking.enums; +package ru.practicum.server.shareit.entity.booking.enums; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/comment/CommentRepository.java b/server/src/main/java/ru/practicum/server/shareit/entity/comment/CommentRepository.java new file mode 100644 index 0000000..867bcd6 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/comment/CommentRepository.java @@ -0,0 +1,17 @@ +package ru.practicum.server.shareit.entity.comment; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.practicum.server.shareit.entity.comment.model.Comment; + +import java.util.List; + +public interface CommentRepository extends JpaRepository { + List findByItemId(Long itemId); + + @Modifying + @Query("DELETE FROM Comment c WHERE c.author.id = :userId") + void deleteByAuthorId(@Param("userId") Long userId); +} diff --git a/src/main/java/ru/practicum/shareit/entity/comment/model/Comment.java b/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/Comment.java similarity index 80% rename from src/main/java/ru/practicum/shareit/entity/comment/model/Comment.java rename to server/src/main/java/ru/practicum/server/shareit/entity/comment/model/Comment.java index 100c1e2..0ac85f8 100644 --- a/src/main/java/ru/practicum/shareit/entity/comment/model/Comment.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/Comment.java @@ -1,12 +1,12 @@ -package ru.practicum.shareit.entity.comment.model; +package ru.practicum.server.shareit.entity.comment.model; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import ru.practicum.shareit.entity.item.model.Item; -import ru.practicum.shareit.entity.user.model.User; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.user.model.User; import java.time.LocalDateTime; diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/CommentDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/CommentDto.java new file mode 100644 index 0000000..d49ca2b --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/CommentDto.java @@ -0,0 +1,17 @@ +package ru.practicum.server.shareit.entity.comment.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CommentDto { + private Long id; + private String text; + private String authorName; + private LocalDateTime created; +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/CommentMapper.java b/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/CommentMapper.java new file mode 100644 index 0000000..1629b99 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/comment/model/CommentMapper.java @@ -0,0 +1,17 @@ +package ru.practicum.server.shareit.entity.comment.model; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring", + injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface CommentMapper { + + @Mapping(source = "comment.id", target = "id") + @Mapping(source = "author.name", target = "authorName") + CommentDto toCommentDto(Comment comment); + + @Mapping(source = "authorName", target = "author.name") + Comment toComment(CommentDto commentDto); +} diff --git a/src/main/java/ru/practicum/shareit/entity/item/ItemController.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/ItemController.java similarity index 76% rename from src/main/java/ru/practicum/shareit/entity/item/ItemController.java rename to server/src/main/java/ru/practicum/server/shareit/entity/item/ItemController.java index 0be69db..24b5190 100644 --- a/src/main/java/ru/practicum/shareit/entity/item/ItemController.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/ItemController.java @@ -1,30 +1,27 @@ -package ru.practicum.shareit.entity.item; +package ru.practicum.server.shareit.entity.item; import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.entity.comment.model.CommentDto; -import ru.practicum.shareit.entity.item.model.dto.ItemDto; +import ru.practicum.server.shareit.entity.comment.model.CommentDto; +import ru.practicum.server.shareit.entity.item.dto.ItemDto; +import ru.practicum.server.shareit.entity.item.services.ItemServiceImpl; import java.util.List; import java.util.Map; @RestController +@RequiredArgsConstructor @RequestMapping("/items") public class ItemController { private final ItemServiceImpl service; - @Autowired - public ItemController(ItemServiceImpl service) { - this.service = service; - } - @GetMapping("/{id}") @ResponseStatus(HttpStatus.OK) - public ItemDto getById(@PathVariable Long id, - @RequestHeader("X-Sharer-User-Id") Long userId) { + public ItemDto getItemById(@PathVariable Long id, + @RequestHeader("X-Sharer-User-Id") Long userId) { return service.getItemDtoWithBookingsAndComments(userId, id); } @@ -60,7 +57,7 @@ public ItemDto update(@PathVariable Long itemId, public CommentDto addComment( @PathVariable Long itemId, @RequestHeader("X-Sharer-User-Id") Long userId, - @RequestBody @Valid CommentDto commentDto) { + @RequestBody CommentDto commentDto) { return service.addComment(userId, itemId, commentDto); } diff --git a/src/main/java/ru/practicum/shareit/entity/item/ItemRepository.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/ItemRepository.java similarity index 77% rename from src/main/java/ru/practicum/shareit/entity/item/ItemRepository.java rename to server/src/main/java/ru/practicum/server/shareit/entity/item/ItemRepository.java index 59d4363..2e3e00e 100644 --- a/src/main/java/ru/practicum/shareit/entity/item/ItemRepository.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/ItemRepository.java @@ -1,8 +1,8 @@ -package ru.practicum.shareit.entity.item; +package ru.practicum.server.shareit.entity.item; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import ru.practicum.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.item.model.Item; import java.util.List; public interface ItemRepository extends JpaRepository { @@ -15,4 +15,6 @@ public interface ItemRepository extends JpaRepository { "LOWER(i.description) LIKE LOWER(CONCAT('%', ?1, '%')))" ) List searchAvailableItems(String text); + + List findByRequestId(Long requestId); } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/dto/ItemDto.java new file mode 100644 index 0000000..106ac84 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/dto/ItemDto.java @@ -0,0 +1,37 @@ +package ru.practicum.server.shareit.entity.item.dto; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.server.shareit.entity.booking.dto.BookingDto; +import ru.practicum.server.shareit.entity.comment.model.CommentDto; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemDto { + private Long id; + + @NotBlank(message = "Название не может быть пустым") + private String name; + + @NotBlank(message = "Описание не может быть пустым") + private String description; + + @NotNull(message = "Статус доступности обязателен") + private Boolean available; + + private BookingDto lastBooking; + + private BookingDto nextBooking; + + private List comments; + + @Nullable + private Long requestId; +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/item/dto/ItemMapper.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/dto/ItemMapper.java new file mode 100644 index 0000000..177bad6 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/dto/ItemMapper.java @@ -0,0 +1,30 @@ +package ru.practicum.server.shareit.entity.item.dto; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import ru.practicum.server.shareit.entity.booking.dto.BookingDto; +import ru.practicum.server.shareit.entity.booking.dto.BookingMapper; +import ru.practicum.server.shareit.entity.comment.model.CommentDto; +import ru.practicum.server.shareit.entity.comment.model.CommentMapper; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.itemRequest.ItemRequestMapper; + +import java.util.List; + +@Mapper(componentModel = "spring", + uses = {BookingMapper.class, CommentMapper.class, ItemRequestMapper.class}, + injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface ItemMapper { + + ItemDto toItemDto(Item item); + + @Mapping(source = "item.id", target = "id") + @Mapping(source = "lastBooking", target = "lastBooking") + @Mapping(source = "nextBooking", target = "nextBooking") + @Mapping(source = "item.request.id", target = "requestId") + @Mapping(source = "comments", target = "comments") + ItemDto toItemDto(Item item, BookingDto lastBooking, BookingDto nextBooking, List comments); + + Item toItem(ItemDto itemDto); +} diff --git a/src/main/java/ru/practicum/shareit/entity/item/model/Item.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/model/Item.java similarity index 63% rename from src/main/java/ru/practicum/shareit/entity/item/model/Item.java rename to server/src/main/java/ru/practicum/server/shareit/entity/item/model/Item.java index 08e7e4e..75511f1 100644 --- a/src/main/java/ru/practicum/shareit/entity/item/model/Item.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/model/Item.java @@ -1,10 +1,15 @@ -package ru.practicum.shareit.entity.item.model; +package ru.practicum.server.shareit.entity.item.model; import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.*; -import ru.practicum.shareit.entity.user.model.User; +import ru.practicum.server.shareit.entity.comment.model.Comment; +import ru.practicum.server.shareit.entity.itemRequest.ItemRequest; +import ru.practicum.server.shareit.entity.user.model.User; + +import java.util.ArrayList; +import java.util.List; @Entity @Table(name = "items", schema = "public") @@ -34,10 +39,10 @@ public class Item { @JoinColumn(name = "owner_id", nullable = false) private User owner; -// @ManyToOne(fetch = FetchType.LAZY) -// @JoinColumn(name = "request_id") -// private ItemRequest request; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "request_id") + private ItemRequest request; - @Column(name = "request_id") - private Long requestId; + @OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/item/model/ItemResponseDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/model/ItemResponseDto.java new file mode 100644 index 0000000..6f5df35 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/model/ItemResponseDto.java @@ -0,0 +1,14 @@ +package ru.practicum.server.shareit.entity.item.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class ItemResponseDto { + private Long itemId; + private String name; + private Long ownerId; +} diff --git a/src/main/java/ru/practicum/shareit/entity/item/ItemService.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/services/ItemService.java similarity index 67% rename from src/main/java/ru/practicum/shareit/entity/item/ItemService.java rename to server/src/main/java/ru/practicum/server/shareit/entity/item/services/ItemService.java index 3ed56ac..1166dba 100644 --- a/src/main/java/ru/practicum/shareit/entity/item/ItemService.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/services/ItemService.java @@ -1,8 +1,8 @@ -package ru.practicum.shareit.entity.item; +package ru.practicum.server.shareit.entity.item.services; -import ru.practicum.shareit.entity.comment.model.CommentDto; -import ru.practicum.shareit.entity.item.model.Item; -import ru.practicum.shareit.entity.item.model.dto.ItemDto; +import ru.practicum.server.shareit.entity.comment.model.CommentDto; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.item.dto.ItemDto; import java.util.List; import java.util.Map; diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/item/services/ItemServiceImpl.java b/server/src/main/java/ru/practicum/server/shareit/entity/item/services/ItemServiceImpl.java new file mode 100644 index 0000000..6f8d16c --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/item/services/ItemServiceImpl.java @@ -0,0 +1,249 @@ +package ru.practicum.server.shareit.entity.item.services; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.server.shareit.entity.booking.BookingRepository; +import ru.practicum.server.shareit.entity.booking.dto.BookingDto; +import ru.practicum.server.shareit.entity.booking.dto.BookingMapper; +import ru.practicum.server.shareit.entity.comment.CommentRepository; +import ru.practicum.server.shareit.entity.comment.model.Comment; +import ru.practicum.server.shareit.entity.comment.model.CommentDto; +import ru.practicum.server.shareit.entity.comment.model.CommentMapper; +import ru.practicum.server.shareit.entity.item.ItemRepository; +import ru.practicum.server.shareit.entity.item.dto.ItemMapper; +import ru.practicum.server.shareit.entity.itemRequest.ItemRequest; +import ru.practicum.server.shareit.entity.itemRequest.ItemRequestRepository; +import ru.practicum.server.shareit.entity.user.model.User; +import ru.practicum.server.shareit.exception.NotFoundException; +import ru.practicum.server.shareit.entity.item.dto.ItemDto; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.user.UserRepository; +import ru.practicum.server.shareit.exception.ValidationException; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ItemServiceImpl implements ItemService { + private final ItemRepository itemRepository; + private final ItemMapper itemMapper; + private final UserRepository userRepository; + private final BookingRepository bookingRepository; + private final BookingMapper bookingMapper; + private final CommentRepository commentRepository; + private final CommentMapper commentMapper; + private final ItemRequestRepository itemRequestRepository; + + @Override + public ItemDto getItemDtoById(Long id) { + log.info("Получение информации о вещи с ID={}", id); + Item item = itemRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Вещь с ID " + id + " не найдена")); + ItemDto itemDto = itemMapper.toItemDto(item); + log.info("Получена информация о вещи: {}", itemDto); + return itemDto; + } + + @Override + public Item getItemById(Long id) { + log.info("Получение сущности вещи с ID={}", id); + Item item = itemRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Вещь с ID " + id + " не найдена")); + log.info("Получена сущность вещи: {}", item); + return item; + } + + @Override + public List getItemByUserId(Long userId) { + log.info("Получение списка вещей пользователя с ID={}", userId); + if (!userRepository.existsById(userId)) { + log.error("Пользователь с ID={} не найден", userId); + throw new NotFoundException("Пользователь с ID " + userId + " не найден"); + } + List items = itemRepository.findByOwnerId(userId).stream() + .map(itemMapper::toItemDto) + .collect(Collectors.toList()); + log.info("Получено {} вещей для пользователя с ID={}", items.size(), userId); + return items; + } + + @Override + public List searchText(String text) { + log.info("Поиск вещей по тексту: {}", text); + if (text == null || text.isBlank()) { + log.info("Поисковый запрос пуст, возвращается пустой список"); + return List.of(); + } + List items = itemRepository.searchAvailableItems(text.toLowerCase()).stream() + .map(itemMapper::toItemDto) + .collect(Collectors.toList()); + log.info("Найдено {} вещей по запросу: {}", items.size(), text); + return items; + } + + @Override + @Transactional + public ItemDto create(Long userId, ItemDto itemDto) { + log.info("Создание новой вещи для пользователя с ID={}, данные: {}", userId, itemDto); + User owner = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); + + ItemRequest request = Optional.ofNullable(itemDto.getRequestId()) + .flatMap(itemRequestRepository::findById) + .orElse(null); + + Item item = itemMapper.toItem(itemDto); + item.setOwner(owner); + item.setRequest(request); + Item savedItem = itemRepository.save(item); + ItemDto savedItemDto = itemMapper.toItemDto(savedItem); + log.info("Создана новая вещь: {}", savedItemDto); + return savedItemDto; + } + + @Override + @Transactional + public ItemDto update(Long itemId, Long userId, Map updates) { + log.info("Обновление вещи с ID={} для пользователя с ID={}, данные: {}", itemId, userId, updates); + Item item = itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + " не найдена")); + if (!item.getOwner().getId().equals(userId)) { + log.error("Попытка редактирования вещи пользователем, который не является владельцем"); + throw new NotFoundException("Редактировать вещь может только владелец"); + } + updates.forEach((key, value) -> { + switch (key) { + case "name": + if (value != null) { + log.debug("Обновление названия вещи на {}", value); + item.setName((String) value); + } + break; + case "description": + if (value != null) { + log.debug("Обновление описания вещи на {}", value); + item.setDescription((String) value); + } + break; + case "available": + if (value != null) { + log.debug("Обновление доступности вещи на {}", value); + item.setAvailable((Boolean) value); + } + break; + } + }); + Item updatedItem = itemRepository.save(item); + ItemDto updatedItemDto = itemMapper.toItemDto(updatedItem); + log.info("Вещь обновлена: {}", updatedItemDto); + return updatedItemDto; + } + + @Override + @Transactional + public CommentDto addComment(Long userId, Long itemId, CommentDto commentDto) { + log.info("Добавление комментария к вещи с ID={} от пользователя с ID={}, текст: {}", + itemId, userId, commentDto.getText()); + + User author = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); + Item item = itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + " не найдена")); + + LocalDateTime now = LocalDateTime.now(); + + if (commentDto.getText() == null || commentDto.getText().isBlank()) { + throw new ValidationException("Текст комментария не может быть пустым"); + } + + BookingDto lastBooking = bookingRepository.findLastBookingForItem(itemId, userId, now, PageRequest.of(0, 1, Sort.by("end").descending())) + .stream() + .findFirst() + .map(bookingMapper::toBookingDto) + .orElse(null); + + // ПРАВИЛЬНАЯ ЛОГИКА: + // 1. Если бронирования нет - нельзя комментировать + // 2. Если бронирование еще не завершилось - нельзя комментировать + // 3. Если бронирование не APPROVED - нельзя комментировать + + if (lastBooking == null) { + throw new ValidationException("У пользователя не было бронирований этого предмета"); + } + + if (lastBooking.getEnd().isAfter(now)) { + throw new ValidationException("Нельзя комментировать предмет до завершения бронирования"); + } + + if (!"APPROVED".equals(lastBooking.getStatus())) { + throw new ValidationException("Можно комментировать только подтвержденные бронирования"); + } + + Comment comment = new Comment(null, + commentDto.getText(), + item, + author, + LocalDateTime.now()); + + Comment savedComment = commentRepository.save(comment); + CommentDto savedCommentDto = commentMapper.toCommentDto(savedComment); + log.info("Добавлен комментарий: {}", savedCommentDto); + return savedCommentDto; + } + + @Override + public ItemDto getItemDtoWithBookingsAndComments(Long userId, Long itemId) { + Item item = itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + " не найдена")); + + LocalDateTime now = LocalDateTime.now(); + BookingDto lastBooking = null; + BookingDto nextBooking = null; + + if (item.getOwner().getId().equals(userId)) { + lastBooking = bookingRepository + .findLastBookingForItem(itemId, userId, now, PageRequest.of(0, 1)) + .stream() + .findFirst() + .map(bookingMapper::toBookingDto) + .orElse(null); + + nextBooking = bookingRepository + .findNextBookingForItem(itemId, now, PageRequest.of(0, 1)) + .stream() + .findFirst() + .map(bookingMapper::toBookingDto) + .orElse(null); + } + + List comments = commentRepository.findByItemId(itemId).stream() + .map(comment -> { + CommentDto commentDto = new CommentDto(); + commentDto.setId(comment.getId()); + commentDto.setText(comment.getText()); + commentDto.setAuthorName(comment.getAuthor().getName()); + commentDto.setCreated(comment.getCreated()); + return commentDto; + }).collect(Collectors.toList()); + + return itemMapper.toItemDto(item, lastBooking, nextBooking, comments); + } + +// private Optional getLastBooking(List userBookings) { +// return userBookings.stream() +// .sorted(Comparator.comparing(Booking::getEnd).reversed()) // Сортируем по дате окончания (по убыванию) +// .findFirst(); // Берём первое (последнее по времени) +// } + + +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequest.java b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequest.java new file mode 100644 index 0000000..16c3e1c --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequest.java @@ -0,0 +1,30 @@ +package ru.practicum.server.shareit.entity.itemRequest; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.server.shareit.entity.user.model.User; + +import java.time.LocalDateTime; + +@Data +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "item_requests", schema = "public") +public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(nullable = false) + private String description; + + @Column(name = "created", nullable = false) + private LocalDateTime created; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "requester_id", nullable = false) + private User requester; +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestController.java b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestController.java new file mode 100644 index 0000000..a678419 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestController.java @@ -0,0 +1,44 @@ +package ru.practicum.server.shareit.entity.itemRequest; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping(path = "/requests") +public class ItemRequestController { + + private final ItemRequestService requestService; + + @GetMapping("/{requestId}") + @ResponseStatus(HttpStatus.OK) + public ItemRequestDto getRequestById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable Long requestId) { + return requestService.getRequestById(userId, requestId); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public List getUserRequests(@RequestHeader("X-Sharer-User-Id") Long userId) { + return requestService.getUserRequests(userId); + } + + @GetMapping("/all") + @ResponseStatus(HttpStatus.OK) + public List getAllRequests( + @RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + return requestService.getAllRequests(userId, from, size); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ItemRequestDto createRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody ItemRequestDto itemRequestDto) { + return requestService.createRequest(userId, itemRequestDto); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestDto.java new file mode 100644 index 0000000..593da29 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestDto.java @@ -0,0 +1,17 @@ +package ru.practicum.server.shareit.entity.itemRequest; + +import lombok.Getter; +import lombok.Setter; +import ru.practicum.server.shareit.entity.item.model.ItemResponseDto; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +public class ItemRequestDto { + private long id; + private String description; + private LocalDateTime created; + private List items; +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestMapper.java b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestMapper.java new file mode 100644 index 0000000..2dc12b5 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestMapper.java @@ -0,0 +1,22 @@ +package ru.practicum.server.shareit.entity.itemRequest; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import ru.practicum.server.shareit.entity.item.model.ItemResponseDto; +import java.util.List; + +@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface ItemRequestMapper { + + ItemRequestDto toItemRequestDto(ItemRequest itemRequest); + + @Mapping(target = "requester", ignore = true) + ItemRequest toItemRequest(ItemRequestDto itemRequestDto); + + default ItemRequestDto toItemRequestDtoWithItems(ItemRequest itemRequest, List items) { + ItemRequestDto dto = toItemRequestDto(itemRequest); + dto.setItems(items); + return dto; + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestRepository.java b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestRepository.java new file mode 100644 index 0000000..118893d --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestRepository.java @@ -0,0 +1,13 @@ +package ru.practicum.server.shareit.entity.itemRequest; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ItemRequestRepository extends JpaRepository { + + List findByRequesterIdOrderByCreatedDesc(Long requesterId); + + List findByRequesterIdNotOrderByCreatedDesc(Long requesterId, Pageable pageable); +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestService.java b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestService.java new file mode 100644 index 0000000..1b437d6 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestService.java @@ -0,0 +1,14 @@ +package ru.practicum.server.shareit.entity.itemRequest; + +import java.util.List; + +public interface ItemRequestService { + + ItemRequestDto getRequestById(Long userId, Long requestId); + + List getUserRequests(Long userId); + + List getAllRequests(Long userId,Integer from,Integer size); + + ItemRequestDto createRequest(Long userId, ItemRequestDto itemRequestDto); +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestServiceImpl.java new file mode 100644 index 0000000..e993de4 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/itemRequest/ItemRequestServiceImpl.java @@ -0,0 +1,117 @@ +package ru.practicum.server.shareit.entity.itemRequest; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.server.shareit.entity.item.ItemRepository; +import ru.practicum.server.shareit.entity.item.model.ItemResponseDto; +import ru.practicum.server.shareit.entity.item.model.Item; +import ru.practicum.server.shareit.entity.user.UserRepository; +import ru.practicum.server.shareit.entity.user.model.User; +import ru.practicum.server.shareit.exception.NotFoundException; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ItemRequestServiceImpl implements ItemRequestService { + + private final ItemRequestRepository itemRequestRepository; + private final ItemRepository itemRepository; + private final UserRepository userRepository; + private final ItemRequestMapper itemRequestMapper; + + @Override + public ItemRequestDto getRequestById(Long userId, Long requestId) { + log.info("Получение запроса на вещь с id={}", requestId); + + userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь не найден!")); + + ItemRequest request = itemRequestRepository.findById(requestId) + .orElseThrow(() -> new RuntimeException("Запрос не найден")); + + List relatedItems = itemRepository.findByRequestId(requestId); + log.info("Найдено {} вещей, связанных с запросом id={}", relatedItems.size(), requestId); + List itemResponseDtos = getItemResponses(relatedItems); + ItemRequestDto result = itemRequestMapper.toItemRequestDtoWithItems(request, itemResponseDtos); + log.info("Получен запрос на вещь: {}", result); + return result; + } + + @Override + public List getUserRequests(Long userId) { + log.info("Получение всех запросов пользователя с id={}", userId); + List requests = itemRequestRepository.findByRequesterIdOrderByCreatedDesc(userId); + log.info("Найдено {} запросов для пользователя с id={}", requests.size(), userId); + return requests.stream() + .map(request -> { + List answers = getItemResponses(itemRepository.findByRequestId(request.getId())); + ItemRequestDto requestDto = itemRequestMapper.toItemRequestDtoWithItems(request, answers); + log.info("Обработан запрос с id={}", request.getId()); + return requestDto; + }) + .collect(Collectors.toList()); + } + + @Override + public List getAllRequests(Long userId, Integer from, Integer size) { + log.info("Получение всех запросов, кроме пользователя с id={}, начиная с {} по {}", userId, from, size); + Pageable pageable = PageRequest.of(from / size, size); + List requests = itemRequestRepository.findByRequesterIdNotOrderByCreatedDesc(userId, pageable); + log.info("Найдено {} запросов", requests.size()); + return requests.stream() + .map(request -> { + List answers = itemRepository.findByRequestId(request.getId()) + .stream() + .map(item -> new ItemResponseDto( + item.getId(), + item.getName(), + item.getOwner().getId() + )) + .collect(Collectors.toList()); + ItemRequestDto requestDto = itemRequestMapper.toItemRequestDtoWithItems(request, answers); + log.info("Обработан запрос с id={}", request.getId()); + return requestDto; + }) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public ItemRequestDto createRequest(Long userId, ItemRequestDto itemRequestDto) { + log.info("Создание нового запроса на вещь от пользователя с id={}", userId); + ItemRequest request = itemRequestMapper.toItemRequest(itemRequestDto); + + User owner = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь не найден!")); + + request.setRequester(owner); + request.setCreated(LocalDateTime.now()); + ItemRequest savedRequest = itemRequestRepository.save(request); + log.info("Создан запрос на вещь с id={}", savedRequest.getId()); + return itemRequestMapper.toItemRequestDtoWithItems(savedRequest, List.of()); + } + + private List getItemResponses(List relatedItems) { + log.info("Формирование списка ответов для {} вещей", relatedItems.size()); + return relatedItems.stream() + .map(item -> { + ItemResponseDto itemResponseDto = new ItemResponseDto( + item.getId(), + item.getName(), + item.getOwner().getId() + ); + log.debug("Создан ответ для вещи с id={}", item.getId()); + return itemResponseDto; + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/practicum/shareit/entity/user/UserController.java b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserController.java similarity index 74% rename from src/main/java/ru/practicum/shareit/entity/user/UserController.java rename to server/src/main/java/ru/practicum/server/shareit/entity/user/UserController.java index d8323fc..caf6525 100644 --- a/src/main/java/ru/practicum/shareit/entity/user/UserController.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserController.java @@ -1,30 +1,33 @@ -package ru.practicum.shareit.entity.user; +package ru.practicum.server.shareit.entity.user; import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.entity.user.model.dto.UserDto; +import ru.practicum.server.shareit.entity.user.model.dto.UserDto; +import java.util.List; import java.util.Map; @RestController +@RequiredArgsConstructor @RequestMapping(path = "/users") public class UserController { private final UserService service; - @Autowired - public UserController(UserService service) { - this.service = service; - } - @GetMapping("/{id}") @ResponseStatus(HttpStatus.OK) public UserDto getUser(@PathVariable Long id) { return service.getUserDto(id); } + @GetMapping + @ResponseStatus(HttpStatus.OK) + public List getUserList() { + return service.getUserList(); + } + @PostMapping @ResponseStatus(HttpStatus.CREATED) public UserDto create(@RequestBody @Valid UserDto user) { diff --git a/src/main/java/ru/practicum/shareit/entity/user/UserRepository.java b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserRepository.java similarity index 68% rename from src/main/java/ru/practicum/shareit/entity/user/UserRepository.java rename to server/src/main/java/ru/practicum/server/shareit/entity/user/UserRepository.java index cbbb964..b9c20e3 100644 --- a/src/main/java/ru/practicum/shareit/entity/user/UserRepository.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserRepository.java @@ -1,7 +1,7 @@ -package ru.practicum.shareit.entity.user; +package ru.practicum.server.shareit.entity.user; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.shareit.entity.user.model.User; +import ru.practicum.server.shareit.entity.user.model.User; public interface UserRepository extends JpaRepository { diff --git a/src/main/java/ru/practicum/shareit/entity/user/UserService.java b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserService.java similarity index 60% rename from src/main/java/ru/practicum/shareit/entity/user/UserService.java rename to server/src/main/java/ru/practicum/server/shareit/entity/user/UserService.java index 3153052..af4a40c 100644 --- a/src/main/java/ru/practicum/shareit/entity/user/UserService.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserService.java @@ -1,8 +1,8 @@ -package ru.practicum.shareit.entity.user; +package ru.practicum.server.shareit.entity.user; -import ru.practicum.shareit.entity.user.model.User; -import ru.practicum.shareit.entity.user.model.dto.UserDto; -import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.server.shareit.entity.user.model.User; +import ru.practicum.server.shareit.entity.user.model.dto.UserDto; +import ru.practicum.server.shareit.exception.NotFoundException; import java.util.List; import java.util.Map; diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/user/UserServiceImpl.java b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserServiceImpl.java new file mode 100644 index 0000000..60b5e3c --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/user/UserServiceImpl.java @@ -0,0 +1,122 @@ +package ru.practicum.server.shareit.entity.user; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.server.shareit.entity.booking.BookingRepository; +import ru.practicum.server.shareit.entity.comment.CommentRepository; +import ru.practicum.server.shareit.entity.user.model.User; +import ru.practicum.server.shareit.entity.user.model.dto.UserMapper; +import ru.practicum.server.shareit.exception.ConflictException; +import ru.practicum.server.shareit.exception.NotFoundException; +import ru.practicum.server.shareit.entity.user.model.dto.UserDto; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + private final BookingRepository bookingRepository; + private final CommentRepository commentRepository; + private final UserMapper userMapper; + + @Override + public List getUserList() { + log.info("Получение списка всех пользователей"); + List users = userRepository.findAll().stream() + .map(userMapper::toUserDto) + .collect(Collectors.toList()); + log.info("Получено {} пользователей", users.size()); + return users; + } + + @Override + public UserDto getUserDto(Long id) { + log.info("Получение пользователя с id={}", id); + User user = userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Пользователя с таким id не существует")); + UserDto userDto = userMapper.toUserDto(user); + log.info("Получен пользователь: {}", userDto); + return userDto; + } + + @Override + public User getUser(Long id) { + log.info("Получение сущности пользователя с id={}", id); + User user = userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Пользователя с таким id не существует")); + log.info("Получена сущность пользователя: {}", user); + return user; + } + + @Override + @Transactional + public UserDto create(UserDto userDto) { + log.info("Создание пользователя: {}", userDto); + if (userRepository.existsByEmail(userDto.getEmail())) { + throw new ConflictException("Пользователь с такой почтой уже существует"); + } + User user = userMapper.toUser(userDto); + User savedUser = userRepository.save(user); + UserDto savedUserDto = userMapper.toUserDto(savedUser); + log.info("Создан пользователь: {}", savedUserDto); + return savedUserDto; + } + + @Override + @Transactional + public UserDto update(Long id, Map updates) { + log.info("Обновление пользователя с id={}, данные для обновления: {}", id, updates); + User user = userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Невозможно обновить пользователя которого нет")); + + if (updates.containsKey("email")) { + String newEmail = (String) updates.get("email"); + log.info("Обновление email для пользователя с id={} на {}", id, newEmail); + if (!newEmail.equals(user.getEmail()) && userRepository.existsByEmailAndIdNot(newEmail, id)) { + throw new ConflictException("Пользователь с такой почтой уже существует"); + } + user.setEmail(newEmail); + } + if (updates.containsKey("name")) { + String newName = (String) updates.get("name"); + log.info("Обновление имени для пользователя с id={} на {}", id, newName); + user.setName(newName); + } + + User updatedUser = userRepository.save(user); + UserDto updatedUserDto = userMapper.toUserDto(updatedUser); + log.info("Обновлённый пользователь: {}", updatedUserDto); + return updatedUserDto; + } + + @Override + @Transactional + public void delete(long id) { + log.info("Удаление пользователя с id={}", id); + if (!userRepository.existsById(id)) { + throw new NotFoundException("Невозможно удалить пользователя которого нет"); + } + bookingRepository.deleteByBookerId(id); + commentRepository.deleteByAuthorId(id); + userRepository.deleteById(id); + + log.info("Пользователь с id={} удалён", id); + } + + @Override + public void validateUserExists(Long id) throws NotFoundException { + log.info("Проверка существования пользователя с id={}", id); + if (!userRepository.existsById(id)) { + throw new NotFoundException("Пользователь с id " + id + " не найден"); + } + log.info("Пользователь с id={} существует", id); + } +} diff --git a/src/main/java/ru/practicum/shareit/entity/user/model/User.java b/server/src/main/java/ru/practicum/server/shareit/entity/user/model/User.java similarity index 90% rename from src/main/java/ru/practicum/shareit/entity/user/model/User.java rename to server/src/main/java/ru/practicum/server/shareit/entity/user/model/User.java index fd02d28..106b9c3 100644 --- a/src/main/java/ru/practicum/shareit/entity/user/model/User.java +++ b/server/src/main/java/ru/practicum/server/shareit/entity/user/model/User.java @@ -1,4 +1,4 @@ -package ru.practicum.shareit.entity.user.model; +package ru.practicum.server.shareit.entity.user.model; import jakarta.persistence.*; import jakarta.validation.constraints.Email; diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/user/model/dto/UserDto.java b/server/src/main/java/ru/practicum/server/shareit/entity/user/model/dto/UserDto.java new file mode 100644 index 0000000..1f3914e --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/user/model/dto/UserDto.java @@ -0,0 +1,21 @@ +package ru.practicum.server.shareit.entity.user.model.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserDto { + private Long id; + private String name; + @Email + @NotBlank + @NotEmpty + private String email; + +} diff --git a/server/src/main/java/ru/practicum/server/shareit/entity/user/model/dto/UserMapper.java b/server/src/main/java/ru/practicum/server/shareit/entity/user/model/dto/UserMapper.java new file mode 100644 index 0000000..575fed8 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/entity/user/model/dto/UserMapper.java @@ -0,0 +1,13 @@ +package ru.practicum.server.shareit.entity.user.model.dto; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import ru.practicum.server.shareit.entity.user.model.User; + +@Mapper(componentModel = "spring", + injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface UserMapper { + UserDto toUserDto(User user); + + User toUser(UserDto userDto); +} diff --git a/server/src/main/java/ru/practicum/server/shareit/exception/ConflictException.java b/server/src/main/java/ru/practicum/server/shareit/exception/ConflictException.java new file mode 100644 index 0000000..6600812 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/exception/ConflictException.java @@ -0,0 +1,7 @@ +package ru.practicum.server.shareit.exception; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/exception/ForbiddenException.java b/server/src/main/java/ru/practicum/server/shareit/exception/ForbiddenException.java new file mode 100644 index 0000000..a4fc361 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package ru.practicum.server.shareit.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/exception/InternalServerException.java b/server/src/main/java/ru/practicum/server/shareit/exception/InternalServerException.java new file mode 100644 index 0000000..a604279 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/exception/InternalServerException.java @@ -0,0 +1,7 @@ +package ru.practicum.server.shareit.exception; + +public class InternalServerException extends RuntimeException { + public InternalServerException(String message) { + super(message); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/exception/NotFoundException.java b/server/src/main/java/ru/practicum/server/shareit/exception/NotFoundException.java new file mode 100644 index 0000000..57bd0b3 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.practicum.server.shareit.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/exception/ValidationException.java b/server/src/main/java/ru/practicum/server/shareit/exception/ValidationException.java new file mode 100644 index 0000000..72389c4 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.server.shareit.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/server/src/main/java/ru/practicum/server/shareit/exception/handling/ErrorHandler.java b/server/src/main/java/ru/practicum/server/shareit/exception/handling/ErrorHandler.java new file mode 100644 index 0000000..f19ec13 --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/exception/handling/ErrorHandler.java @@ -0,0 +1,87 @@ +package ru.practicum.server.shareit.exception.handling; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import ru.practicum.server.shareit.exception.*; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse validation(final ValidationException e) { + log.error("Ошибка валидации: {}", e.getMessage()); + return new ErrorResponse("ERROR[400]: Произошла ошибка ValidationException: ", e.getMessage()); + } + + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse httpMediaTypeNotSupported(final HttpMediaTypeNotSupportedException e) { + log.error("Неподдерживаемый медиа-тип: {}", e.getMessage()); + return new ErrorResponse("ERROR[400]: Произошла ошибка HttpMediaTypeNotSupportedException: ", e.getMessage()); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse httpMessageNotReadable(final HttpMessageNotReadableException e) { + log.error("Ошибка чтения HTTP сообщения: {}", e.getMessage()); + return new ErrorResponse("ERROR[400]: Произошла ошибка HttpMessageNotReadableException: ", e.getMessage()); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse argumentTypeMismatch(final MethodArgumentTypeMismatchException e) { + log.error("Несоответствие типа аргумента: {}", e.getMessage()); + return new ErrorResponse("ERROR[400]: Произошла ошибка MethodArgumentTypeMismatchException: ", e.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse argumentNotValid(MethodArgumentNotValidException e) { + log.error("Некорректный аргумент: {}", e.getMessage()); + return new ErrorResponse("ERROR[400]: Произошла ошибка MethodArgumentTypeMismatchException: ", e.getMessage()); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorResponse forbidden(final ForbiddenException e) { + log.error("Доступ запрещен: {}", e.getMessage()); + return new ErrorResponse("ERROR[403]: Произошла ошибка ForbiddenException: ", e.getMessage()); + } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse notFound(final NotFoundException e) { + log.error("Ресурс не найден: {}", e.getMessage()); + return new ErrorResponse("ERROR[404]: Произошла ошибка NotFoundException: ", e.getMessage()); + } + + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse conflict(ConflictException e) { + log.error("Конфликт данных: {}", e.getMessage()); + return new ErrorResponse("ERROR[409]: Произошла ошибка Conflict: ", e.getMessage()); + } + + @ExceptionHandler(InternalServerException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse internalServerException(final InternalServerException e) { + log.error("Внутренняя ошибка сервера: {}", e.getMessage()); + return new ErrorResponse("ERROR[500]: Произошла ошибка InternalServerException: ", e.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse exception(final Exception e) { + log.error("Непредвиденная ошибка: {}", e.getMessage()); + return new ErrorResponse("ERROR[500]: Произошла ошибка Throwable: ", e.getMessage()); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/server/shareit/exception/handling/ErrorResponse.java b/server/src/main/java/ru/practicum/server/shareit/exception/handling/ErrorResponse.java new file mode 100644 index 0000000..bfd946c --- /dev/null +++ b/server/src/main/java/ru/practicum/server/shareit/exception/handling/ErrorResponse.java @@ -0,0 +1,4 @@ +package ru.practicum.server.shareit.exception.handling; + +public record ErrorResponse(String error, String description) { +} diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties new file mode 100644 index 0000000..50e6db7 --- /dev/null +++ b/server/src/main/resources/application.properties @@ -0,0 +1,31 @@ +spring.sql.init.platform=postgresql +app.timezone=UTC + +spring.datasource.url=jdbc:postgresql://db:5432/shareit + +spring.datasource.username=dbuser +spring.datasource.password=12345 +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +spring.jpa.show-sql=true +logging.level.org.hibernate.SQL=INFO +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE +logging.level.org.hibernate.type.descriptor.sql=TRACE + +spring.datasource.initialization-mode=always +spring.sql.init.mode=always + +logging.level.org.springframework.orm.jpa=DEBUG +logging.level.org.springframework.transaction=INFO +logging.level.org.springframework.transaction.interceptor=TRACE +logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG + +spring.devtools.restart.enabled=true +spring.devtools.livereload.enabled=true +spring.devtools.restart.additional-paths=src/main/java + +server.port=9090 \ No newline at end of file diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 57% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql index 40a6bea..028f87e 100644 --- a/src/main/resources/schema.sql +++ b/server/src/main/resources/schema.sql @@ -1,4 +1,13 @@ -CREATE TABLE IF NOT EXISTS users ( +-- Удаляем таблицы в порядке зависимости +DROP TABLE IF EXISTS comments CASCADE; +DROP TABLE IF EXISTS bookings CASCADE; +DROP TABLE IF EXISTS items CASCADE; +DROP TABLE IF EXISTS item_requests CASCADE; +DROP TABLE IF EXISTS users CASCADE; + +-- Создаём таблицы заново + +CREATE TABLE users ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(512) NOT NULL, @@ -6,7 +15,15 @@ CREATE TABLE IF NOT EXISTS users ( CONSTRAINT UQ_USER_EMAIL UNIQUE (email) ); -CREATE TABLE IF NOT EXISTS items ( +CREATE TABLE item_requests ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + description TEXT NOT NULL, + requester_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + CONSTRAINT fk_author FOREIGN KEY (requester_id) REFERENCES users(id) +); + +CREATE TABLE items ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(512) NOT NULL, @@ -14,15 +31,14 @@ CREATE TABLE IF NOT EXISTS items ( owner_id BIGINT NOT NULL, request_id BIGINT NULL, CONSTRAINT pk_item PRIMARY KEY (id), - CONSTRAINT fk_item_owner FOREIGN KEY (owner_id) REFERENCES users(id) - -- При появлении таблицы item_requests можно добавить внешний ключ: - -- CONSTRAINT fk_item_request FOREIGN KEY (request_id) REFERENCES item_requests(id) + CONSTRAINT fk_item_owner FOREIGN KEY (owner_id) REFERENCES users(id), + CONSTRAINT fk_item_request FOREIGN KEY (request_id) REFERENCES item_requests(id) ); -CREATE TABLE IF NOT EXISTS bookings ( +CREATE TABLE bookings ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - start_time TIMESTAMP NOT NULL, - end_time TIMESTAMP NOT NULL, + start_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + end_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, item_id BIGINT NOT NULL, booker_id BIGINT NOT NULL, status VARCHAR(20) NOT NULL, @@ -32,12 +48,12 @@ CREATE TABLE IF NOT EXISTS bookings ( CONSTRAINT check_valid_booking_time CHECK (end_time > start_time) ); -CREATE TABLE IF NOT EXISTS comments ( +CREATE TABLE comments ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, text TEXT NOT NULL, item_id BIGINT NOT NULL, author_id BIGINT NOT NULL, - created TIMESTAMP NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, CONSTRAINT fk_item FOREIGN KEY (item_id) REFERENCES items(id), CONSTRAINT fk_author FOREIGN KEY (author_id) REFERENCES users(id) -); \ No newline at end of file +); diff --git a/src/main/java/ru/practicum/shareit/entity/booking/BookingServiceImpl.java b/src/main/java/ru/practicum/shareit/entity/booking/BookingServiceImpl.java deleted file mode 100644 index e39ede4..0000000 --- a/src/main/java/ru/practicum/shareit/entity/booking/BookingServiceImpl.java +++ /dev/null @@ -1,153 +0,0 @@ -package ru.practicum.shareit.entity.booking; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.shareit.entity.booking.dto.BookingMapper; -import ru.practicum.shareit.entity.booking.dto.BookingRequestDto; -import ru.practicum.shareit.entity.booking.dto.BookingResponseDto; -import ru.practicum.shareit.entity.booking.enums.Status; -import ru.practicum.shareit.exception.NotFoundException; -import ru.practicum.shareit.exception.ValidationException; -import ru.practicum.shareit.entity.item.ItemService; -import ru.practicum.shareit.entity.item.model.Item; -import ru.practicum.shareit.entity.user.UserService; -import ru.practicum.shareit.entity.user.model.User; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class BookingServiceImpl implements BookingService { - - private final BookingRepository bookingRepository; - - private final UserService userService; - - private final ItemService itemService; - - @Override - @Transactional - public BookingResponseDto createBooking(BookingRequestDto bookingRequestDto, Long userId) { - User booker = userService.getUser(userId); - Item item = itemService.getItemById(bookingRequestDto.getItemId()); - - validateBooking(bookingRequestDto, item, booker); - - Booking booking = BookingMapper.toBooking(bookingRequestDto, item, booker); - booking.setStatus(Status.WAITING); - - return BookingMapper.toResponseDto(bookingRepository.save(booking)); - } - - @Override - @Transactional - public BookingResponseDto approveBooking(Long bookingId, Boolean approved, Long userId) { - Booking booking = bookingRepository.findById(bookingId) - .orElseThrow(() -> new NotFoundException("Бронирование не найдено")); - if (!booking.getItem().getOwner().getId().equals(userId)) { - throw new ValidationException("Подтверждать бронирование может только владелец вещи"); - } - if (booking.getStatus() != Status.WAITING) { - throw new ValidationException("Бронирование уже было обработано"); - } - booking.setStatus(approved ? Status.APPROVED : Status.REJECTED); - return BookingMapper.toResponseDto(bookingRepository.save(booking)); - } - - @Override - public BookingResponseDto getBookingById(Long bookingId, Long userId) { - Booking booking = bookingRepository.findById(bookingId) - .orElseThrow(() -> new NotFoundException("Бронирование не найдено")); - if (!booking.getBooker().getId().equals(userId) && - !booking.getItem().getOwner().getId().equals(userId)) { - throw new ValidationException("Просмотр бронирования доступен только автору или владельцу"); - } - return BookingMapper.toResponseDto(booking); - } - - @Override - public List getUserBookings(Long userId, String state, Integer from, Integer size) { - userService.validateUserExists(userId); - Pageable pageable = PageRequest.of(from / size, size, Sort.by("start").descending()); - LocalDateTime now = LocalDateTime.now(); - switch (state.toUpperCase()) { - case "CURRENT": - return bookingRepository.findCurrentByBookerId(userId, now, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "PAST": - return bookingRepository.findPastByBookerId(userId, now, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "FUTURE": - return bookingRepository.findFutureByBookerId(userId, now, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "WAITING": - case "REJECTED": - Status status = Status.valueOf(state.toUpperCase()); - return bookingRepository.findByBookerIdAndStatusOrderByStartDesc(userId, status, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "ALL": - return bookingRepository.findByBookerIdOrderByStartDesc(userId, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - default: - throw new ValidationException("Unknown state: " + state); - } - } - - @Override - public List getOwnerBookings(Long ownerId, String state, Integer from, Integer size) { - userService.validateUserExists(ownerId); - Pageable pageable = PageRequest.of(from / size, size, Sort.by("start").descending()); - LocalDateTime now = LocalDateTime.now(); - switch (state.toUpperCase()) { - case "CURRENT": - return bookingRepository.findCurrentByOwnerId(ownerId, now, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "PAST": - return bookingRepository.findPastByOwnerId(ownerId, now, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "FUTURE": - return bookingRepository.findFutureByOwnerId(ownerId, now, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "WAITING": - case "REJECTED": - Status status = Status.valueOf(state.toUpperCase()); - return bookingRepository.findByItemOwnerIdAndStatusOrderByStartDesc(ownerId, status, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - case "ALL": - return bookingRepository.findByItemOwnerIdOrderByStartDesc(ownerId, pageable).stream() - .map(BookingMapper::toResponseDto) - .collect(Collectors.toList()); - default: - throw new ValidationException("Unknown state: " + state); - } - } - - private void validateBooking(BookingRequestDto bookingDto, Item item, User booker) { - if (item.getOwner().getId().equals(booker.getId())) { - throw new ValidationException("Владелец не может бронировать свою вещь"); - } - if (!item.getAvailable()) { - throw new ValidationException("Вещь недоступна для бронирования"); - } - if (bookingRepository.existsApprovedBookingsForItemBetweenDates( - item.getId(), bookingDto.getStart(), bookingDto.getEnd())) { - throw new ValidationException("Вещь уже забронирована на указанные даты"); - } - } -} diff --git a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingMapper.java b/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingMapper.java deleted file mode 100644 index 75fe3eb..0000000 --- a/src/main/java/ru/practicum/shareit/entity/booking/dto/BookingMapper.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.practicum.shareit.entity.booking.dto; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import ru.practicum.shareit.entity.booking.Booking; -import ru.practicum.shareit.entity.item.model.Item; -import ru.practicum.shareit.entity.item.model.dto.ItemMapper; -import ru.practicum.shareit.entity.user.model.User; -import ru.practicum.shareit.entity.user.model.dto.UserMapper; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class BookingMapper { - - public static Booking toBooking(BookingRequestDto bookingRequestDto, Item item, User booker) { - Booking booking = new Booking(); - booking.setStart(bookingRequestDto.getStart()); - booking.setEnd(bookingRequestDto.getEnd()); - booking.setItem(item); - booking.setBooker(booker); - return booking; - } - - public static BookingResponseDto toResponseDto(Booking booking) { - return new BookingResponseDto( - booking.getId(), - booking.getStart(), - booking.getEnd(), - booking.getStatus(), - UserMapper.toUserDto(booking.getBooker()), - ItemMapper.toItemDto(booking.getItem()) - ); - } - - public static BookingRequestDto toRequestDto(Booking booking) { - return new BookingRequestDto( - booking.getStart(), - booking.getEnd(), - booking.getItem().getId() - ); - } - - public static BookingDto toDto(Booking booking) { - if (booking == null) return null; - return new BookingDto( - booking.getId(), - booking.getStart(), - booking.getEnd(), - booking.getItem().getId(), - booking.getBooker().getId(), - booking.getStatus().name() - ); - } - - - -} diff --git a/src/main/java/ru/practicum/shareit/entity/comment/CommentRepository.java b/src/main/java/ru/practicum/shareit/entity/comment/CommentRepository.java deleted file mode 100644 index 4f082b1..0000000 --- a/src/main/java/ru/practicum/shareit/entity/comment/CommentRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.practicum.shareit.entity.comment; - -import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.shareit.entity.comment.model.Comment; - -import java.util.List; - -public interface CommentRepository extends JpaRepository { - List findByItemId(Long itemId); -} diff --git a/src/main/java/ru/practicum/shareit/entity/item/ItemRequestController.java b/src/main/java/ru/practicum/shareit/entity/item/ItemRequestController.java deleted file mode 100644 index 07712dd..0000000 --- a/src/main/java/ru/practicum/shareit/entity/item/ItemRequestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.shareit.entity.item; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * TODO Sprint add-item-requests. - */ -@RestController -@RequestMapping(path = "/requests") -public class ItemRequestController { -} diff --git a/src/main/java/ru/practicum/shareit/entity/item/ItemServiceImpl.java b/src/main/java/ru/practicum/shareit/entity/item/ItemServiceImpl.java deleted file mode 100644 index 0b1929b..0000000 --- a/src/main/java/ru/practicum/shareit/entity/item/ItemServiceImpl.java +++ /dev/null @@ -1,171 +0,0 @@ -package ru.practicum.shareit.entity.item; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.shareit.entity.booking.BookingRepository; -import ru.practicum.shareit.entity.booking.dto.BookingDto; -import ru.practicum.shareit.entity.booking.dto.BookingMapper; -import ru.practicum.shareit.entity.comment.CommentRepository; -import ru.practicum.shareit.entity.comment.model.Comment; -import ru.practicum.shareit.entity.comment.model.CommentDto; -import ru.practicum.shareit.entity.user.UserService; -import ru.practicum.shareit.entity.user.model.User; -import ru.practicum.shareit.exception.NotFoundException; -import ru.practicum.shareit.exception.ValidationException; -import ru.practicum.shareit.entity.item.model.dto.ItemDto; -import ru.practicum.shareit.entity.item.model.dto.ItemMapper; -import ru.practicum.shareit.entity.item.model.Item; -import ru.practicum.shareit.entity.user.UserRepository; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class ItemServiceImpl implements ItemService { - - private final ItemRepository itemRepository; - private final UserRepository userRepository; - private final BookingRepository bookingRepository; - private final CommentRepository commentRepository; - private final UserService userService; - - @Override - public ItemDto getItemDtoById(Long id) { - Item item = itemRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Вещь с ID " + id + " не найдена")); - return ItemMapper.toItemDto(item); - } - - @Override - public Item getItemById(Long id) { - return itemRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Вещь с ID " + id + " не найдена")); - } - - @Override - public List getItemByUserId(Long userId) { - if (!userRepository.existsById(userId)) { - throw new NotFoundException("Пользователь с ID " + userId + " не найден"); - } - return itemRepository.findByOwnerId(userId).stream() - .map(ItemMapper::toItemDto) - .collect(Collectors.toList()); - } - - @Override - public List searchText(String text) { - if (text == null || text.isBlank()) { - return List.of(); - } - return itemRepository.searchAvailableItems(text.toLowerCase()).stream() - .map(ItemMapper::toItemDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional - public ItemDto create(Long userId, ItemDto itemDto) { - User owner = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); - Item item = ItemMapper.toItem(itemDto); - item.setOwner(owner); - Item savedItem = itemRepository.save(item); - return ItemMapper.toItemDto(savedItem); - } - - @Override - @Transactional - public ItemDto update(Long itemId, Long userId, Map updates) { - Item item = itemRepository.findById(itemId) - .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + " не найдена")); - if (!item.getOwner().getId().equals(userId)) { - throw new NotFoundException("Редактировать вещь может только владелец"); - } - updates.forEach((key, value) -> { - switch (key) { - case "name": - if (value != null) item.setName((String) value); - break; - case "description": - if (value != null) item.setDescription((String) value); - break; - case "available": - if (value != null) item.setAvailable((Boolean) value); - break; - } - }); - return ItemMapper.toItemDto(itemRepository.save(item)); - } - - @Override - @Transactional - public CommentDto addComment(Long userId, Long itemId, CommentDto commentDto) { - User author = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("Пользователь с ID " + userId + " не найден")); - Item item = itemRepository.findById(itemId) - .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + " не найдена")); - if (!bookingRepository.existsByBookerIdAndItemIdAndEndIsBefore(userId, itemId, LocalDateTime.now())) { - throw new ValidationException("Пользователь не бронировал эту вещь"); - } - Comment comment = new Comment(); - comment.setText(commentDto.getText()); - comment.setItem(item); - comment.setAuthor(author); - comment.setCreated(LocalDateTime.now()); - return toCommentDto(commentRepository.save(comment)); - } - - @Override - public ItemDto getItemDtoWithBookingsAndComments(Long userId, Long itemId) { - Item item = itemRepository.findById(itemId) - .orElseThrow(() -> new NotFoundException("Вещь с ID " + itemId + " не найдена")); - - LocalDateTime now = LocalDateTime.now(); - BookingDto lastBooking = null; - BookingDto nextBooking = null; - - if (item.getOwner().getId().equals(userId)) { - lastBooking = bookingRepository - .findLastBookingForItem(itemId, now, PageRequest.of(0, 1)) - .stream() - .findFirst() - .map(BookingMapper::toDto) - .orElse(null); - - nextBooking = bookingRepository - .findNextBookingForItem(itemId, now, PageRequest.of(0, 1)) - .stream() - .findFirst() - .map(BookingMapper::toDto) - .orElse(null); - } - - List comments = commentRepository.findByItemId(itemId).stream() - .map(comment -> { - CommentDto commentDto = new CommentDto(); - commentDto.setId(comment.getId()); - commentDto.setText(comment.getText()); - commentDto.setAuthorName(comment.getAuthor().getName()); - commentDto.setCreated(comment.getCreated()); - return commentDto; - }).collect(Collectors.toList()); - - return ItemMapper.toItemDto(item, lastBooking, nextBooking, comments); - } - - - private CommentDto toCommentDto(Comment comment) { - CommentDto commentDto = new CommentDto(); - commentDto.setId(comment.getId()); - commentDto.setText(comment.getText()); - commentDto.setAuthorName(comment.getAuthor().getName()); - commentDto.setCreated(comment.getCreated()); - return commentDto; - } -} diff --git a/src/main/java/ru/practicum/shareit/entity/item/model/ItemRequest.java b/src/main/java/ru/practicum/shareit/entity/item/model/ItemRequest.java deleted file mode 100644 index e17f213..0000000 --- a/src/main/java/ru/practicum/shareit/entity/item/model/ItemRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.practicum.shareit.entity.item.model; - -import lombok.Data; -import ru.practicum.shareit.entity.user.model.User; - -import java.time.LocalDateTime; - -/** - * TODO Sprint add-item-requests. - */ - -@Data -public class ItemRequest { - private long id; - private String description; - private User requester; - private LocalDateTime created; -} diff --git a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemMapper.java b/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemMapper.java deleted file mode 100644 index 8756000..0000000 --- a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemMapper.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.practicum.shareit.entity.item.model.dto; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import ru.practicum.shareit.entity.booking.dto.BookingDto; -import ru.practicum.shareit.entity.comment.model.CommentDto; -import ru.practicum.shareit.entity.item.model.Item; - -import java.util.List; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class ItemMapper { - - public static ItemDto toItemDto(Item item) { - ItemDto dto = new ItemDto(); - dto.setId(item.getId()); - dto.setName(item.getName()); - dto.setDescription(item.getDescription()); - dto.setAvailable(item.getAvailable()); - dto.setLastBooking(null); - dto.setNextBooking(null); - dto.setComments(null); - return dto; - } - - - public static ItemDto toItemDto(Item item, - BookingDto lastBooking, - BookingDto nextBooking, - List comments) { - ItemDto dto = new ItemDto(); - dto.setId(item.getId()); - dto.setName(item.getName()); - dto.setDescription(item.getDescription()); - dto.setAvailable(item.getAvailable()); - dto.setLastBooking(lastBooking); - dto.setNextBooking(nextBooking); - dto.setComments(comments); - return dto; - } - - public static Item toItem(ItemDto itemDto) { - Item item = new Item(); - item.setId(itemDto.getId()); - item.setName(itemDto.getName()); - item.setDescription(itemDto.getDescription()); - item.setAvailable(itemDto.getAvailable()); - return item; - } -} diff --git a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemRequestDto.java deleted file mode 100644 index 4333e3d..0000000 --- a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemRequestDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.entity.item.model.dto; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequestDto { -} diff --git a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemWithBookingsDto.java b/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemWithBookingsDto.java deleted file mode 100644 index c6ab544..0000000 --- a/src/main/java/ru/practicum/shareit/entity/item/model/dto/ItemWithBookingsDto.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.practicum.shareit.entity.item.model.dto; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import ru.practicum.shareit.entity.booking.dto.BookingDto; -import ru.practicum.shareit.entity.comment.model.CommentDto; - -import java.util.List; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class ItemWithBookingsDto { - private Long id; - private String name; - private String description; - private Boolean available; - private BookingDto lastBooking; - private BookingDto nextBooking; - private List comments; -} diff --git a/src/main/java/ru/practicum/shareit/entity/user/UserServiceImpl.java b/src/main/java/ru/practicum/shareit/entity/user/UserServiceImpl.java deleted file mode 100644 index 257f410..0000000 --- a/src/main/java/ru/practicum/shareit/entity/user/UserServiceImpl.java +++ /dev/null @@ -1,92 +0,0 @@ -package ru.practicum.shareit.entity.user; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.shareit.entity.user.model.User; -import ru.practicum.shareit.exception.ConflictException; -import ru.practicum.shareit.exception.NotFoundException; -import ru.practicum.shareit.entity.user.model.dto.UserDto; -import ru.practicum.shareit.entity.user.model.dto.UserMapper; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class UserServiceImpl implements UserService { - - private final UserRepository userRepository; - - @Override - public List getUserList() { - return userRepository.findAll().stream() - .map(UserMapper::toUserDto) - .collect(Collectors.toList()); - } - - @Override - public UserDto getUserDto(Long id) { - User user = userRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Пользователя с таким id не существует")); - return UserMapper.toUserDto(user); - } - - @Override - public User getUser(Long id) { - return userRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Пользователя с таким id не существует")); - } - - @Override - @Transactional - public UserDto create(UserDto userDto) { - if (userRepository.existsByEmail(userDto.getEmail())) { - throw new ConflictException("Пользователь с такой почтой уже существует"); - } - - User user = UserMapper.toUser(userDto); - User savedUser = userRepository.save(user); - return UserMapper.toUserDto(savedUser); - } - - @Override - @Transactional - public UserDto update(Long id, Map updates) { - User user = userRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Невозможно обновить пользователя которого нет")); - - if (updates.containsKey("email")) { - String newEmail = (String) updates.get("email"); - if (!newEmail.equals(user.getEmail()) && userRepository.existsByEmailAndIdNot(newEmail, id)) { - throw new ConflictException("Пользователь с такой почтой уже существует"); - } - user.setEmail(newEmail); - } - - if (updates.containsKey("name")) { - user.setName((String) updates.get("name")); - } - - User updatedUser = userRepository.save(user); - return UserMapper.toUserDto(updatedUser); - } - - @Override - @Transactional - public void delete(long id) { - if (!userRepository.existsById(id)) { - throw new NotFoundException("Невозможно удалить пользователя которого нет"); - } - userRepository.deleteById(id); - } - - @Override - public void validateUserExists(Long id) throws NotFoundException { - if (!userRepository.existsById(id)) { - throw new NotFoundException("Пользователь с id " + id + " не найден"); - } - } -} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/entity/user/model/dto/UserMapper.java b/src/main/java/ru/practicum/shareit/entity/user/model/dto/UserMapper.java deleted file mode 100644 index 1a00711..0000000 --- a/src/main/java/ru/practicum/shareit/entity/user/model/dto/UserMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.practicum.shareit.entity.user.model.dto; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import ru.practicum.shareit.entity.user.model.User; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class UserMapper { - public static UserDto toUserDto(User user) { - return new UserDto( - user.getId(), - user.getName(), - user.getEmail() - ); - } - - public static User toUser(UserDto userDto) { - return new User( - userDto.getId(), - userDto.getName(), - userDto.getEmail() - ); - } -} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/tools/Validator.java b/src/main/java/ru/practicum/shareit/tools/Validator.java deleted file mode 100644 index d99246b..0000000 --- a/src/main/java/ru/practicum/shareit/tools/Validator.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.practicum.shareit.tools; - -public class Validator { -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 30f74b2..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,17 +0,0 @@ -spring.jpa.hibernate.ddl-auto=none -spring.jpa.properties.hibernate.format_sql=true -spring.sql.init.mode=always - -logging.level.org.springframework.orm.jpa=INFO -logging.level.org.springframework.transaction=INFO -logging.level.org.springframework.transaction.interceptor=TRACE -logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG - -spring.datasource.url=jdbc:postgresql://localhost:5432/shareit -spring.datasource.username=dbuser -spring.datasource.password=12345 -spring.datasource.driver-class-name=org.postgresql.Driver - -hibernate.show_sql=true -hibernate.jdbc.time_zone=UTC -