diff --git a/README.md b/README.md index 6ef090e..544554e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # java-shareit учебный проект -## Спринт №15 +## Спринт №16 +Добавляем запросы на вещи и "шлюз" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..abe6570 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +services: + gateway: + build: gateway + image: shareit-gateway + container_name: shareit-gateway + ports: + - "8080:8080" + depends_on: + - server + environment: + - SHAREIT_SERVER_URL=http://server:9090 + + server: + build: server + image: shareit-server + container_name: shareit-server + ports: + - "9090:9090" + depends_on: + - db + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit + - SPRING_DATASOURCE_USERNAME=shareit + - SPRING_DATASOURCE_PASSWORD=shareit + + db: + image: postgres:16.1 + container_name: postgres + ports: + - "6541:5432" + environment: + - POSTGRES_PASSWORD=shareit + - POSTGRES_USER=shareit + - POSTGRES_DB=shareit + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 \ No newline at end of file diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=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..f3394c1 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-gateway + 0.0.1-SNAPSHOT + + ShareIt Gateway + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.hibernate.validator + hibernate-validator + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java new file mode 100644 index 0000000..4cbc16c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItGateway { + public static void main(String[] args) { + SpringApplication.run(ShareItGateway.class, args); + } + +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java b/gateway/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java new file mode 100644 index 0000000..ccecd4a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java @@ -0,0 +1,52 @@ +package ru.practicum.shareit.advisor; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +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 java.util.List; +import java.util.stream.Collectors; + +/** + * Класс обработки исключений при обработке поступивших http запросов + */ +@Slf4j +@RestControllerAdvice +public class ErrorAdvisor { + + /** + * Обработка исключения MethodArgumentNotValidException - при проверке аргумента метода + * + * @param e - исключение + * @return - список нарушений для отображения в теле ответа + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorMessage onMethodArgumentNotValidException( + MethodArgumentNotValidException e + ) { + final List violations = e.getBindingResult().getFieldErrors().stream() + .map(error -> new ErrorMessage("[" + error.getField() + "] " + + error.getDefaultMessage())) + .collect(Collectors.toList()); + log.error("400 {}.", e.getMessage()); + return violations.get(0); + } + + /** + * Обработка непредвиденного исключения + * + * @param e - исключение + * @return - сообщение об ошибке + */ + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorMessage handleException(final Exception e) { + log.error("Error", e); + return new ErrorMessage(e.getMessage()); + } +} + diff --git a/src/main/java/ru/practicum/shareit/advisor/ErrorMessage.java b/gateway/src/main/java/ru/practicum/shareit/advisor/ErrorMessage.java similarity index 100% rename from src/main/java/ru/practicum/shareit/advisor/ErrorMessage.java rename to gateway/src/main/java/ru/practicum/shareit/advisor/ErrorMessage.java diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java new file mode 100644 index 0000000..8fed5c4 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.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.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.booking.dto.BookItemRequestDto; +import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.client.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() + ); + } + + public ResponseEntity bookItem(long userId, BookItemRequestDto requestDto) { + return post("", userId, requestDto); + } + + public ResponseEntity getBookings(long userId, BookingState 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); + } + + public ResponseEntity getBooking(long userId, Long bookingId) { + return get("/" + bookingId, userId); + } + + public ResponseEntity approvedBooking(Long id, Long editorId, Boolean approved) { + Map parameters = Map.of("approved", approved); + return patch("/" + id + "?approved={approved}", editorId, parameters, null); + } + + public ResponseEntity findBookingsByOwner(Long ownerId, + BookingState 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); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java new file mode 100644 index 0000000..5c7d2b1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,107 @@ +package ru.practicum.shareit.booking; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.BookItemRequestDto; +import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.validator.ValidAction; + +/** + * Обработка запроса на бронирование вещей + */ +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Slf4j +@Validated +public class BookingController { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + private final BookingClient bookingClient; + + /** + * Ищем бронирования в соответствии со статусом + * + * @param userId - идентификатор пользователя + * @param stateParam - статус бронирования + * @param from + * @param size + * @return - результат поиска + */ + @GetMapping + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getBookings(@RequestHeader(HEADER_USER_ID) long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Неизвестный режим: " + stateParam)); + log.info("Ищем запросы state {}, userId={}, from={}, size={}", stateParam, userId, from, size); + return bookingClient.getBookings(userId, state, from, size); + } + + /** + * Бронирование вещии + * + * @param userId - идентификатор пользователя + * @param requestDto - данные о бронировании + * @return - созданный объект бронирования + */ + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity bookItem(@RequestHeader(HEADER_USER_ID) long userId, + @Validated(ValidAction.OnCreate.class) + @RequestBody BookItemRequestDto requestDto) { + log.info("Создаем запрос на бронирование {}, userId={}", requestDto, userId); + return bookingClient.bookItem(userId, requestDto); + } + + /** + * Редактирукм запрос на бронироапние + */ + @PatchMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity editBooking( + @RequestHeader(HEADER_USER_ID) final Long editorId, + @PathVariable Long id, + @RequestParam Boolean approved) { + log.info("Пользователь id={} редактирует запрос на бронирование id={}, approved={}", + editorId, id, approved); + return bookingClient.approvedBooking(id, editorId, approved); + } + + /** + * Ищем запрос на бронирование + * + * @param userId - идентификатор пользователя + * @param bookingId - идентификатор бронирования + * @return - результат поиска + */ + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{bookingId}") + public ResponseEntity getBooking(@RequestHeader(HEADER_USER_ID) long userId, + @PathVariable Long bookingId) { + log.info("Get booking {}, userId={}", bookingId, userId); + return bookingClient.getBooking(userId, bookingId); + } + + @GetMapping("/owner") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findBookingByOwner( + @RequestHeader(HEADER_USER_ID) final Long ownerId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Неизвестный режим: " + stateParam)); + log.info("Пользователь id={} просматривает запроcы на все свои вещи. state {}, userId={}, from={}, size={}", + ownerId, state, from, size); + return bookingClient.findBookingsByOwner(ownerId, state, from, size); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java new file mode 100644 index 0000000..9a3134e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.booking.dto; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class BookItemRequestDto { + private long itemId; + @FutureOrPresent + private LocalDateTime start; + @Future + private LocalDateTime end; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java similarity index 92% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java index 5a76380..abdfa6a 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -2,9 +2,9 @@ import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; -import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; -import ru.practicum.shareit.booking.enums.BookingStatus; +import lombok.Setter; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.validator.ValidAction; @@ -14,7 +14,8 @@ /** * DTO класс бронирования */ -@Data +@Setter +@Getter @NoArgsConstructor @AllArgsConstructor public class BookingDto { @@ -34,3 +35,4 @@ public class BookingDto { private BookingStatus status = BookingStatus.WAITING; } + diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java new file mode 100644 index 0000000..943af85 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.booking.dto; + +import java.util.Optional; + +public enum BookingState { + // Все + ALL, + // Текущие + CURRENT, + // Будущие + FUTURE, + // Завершенные + PAST, + // Отклоненные + REJECTED, + // Ожидающие подтверждения + WAITING; + + public static Optional from(String stringState) { + for (BookingState state : values()) { + if (state.name().equalsIgnoreCase(stringState)) { + return Optional.of(state); + } + } + return Optional.empty(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatus.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatus.java new file mode 100644 index 0000000..9a32258 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatus.java @@ -0,0 +1,8 @@ +package ru.practicum.shareit.booking.dto; + +public enum BookingStatus { + WAITING, // новое бронирование + APPROVED, // бронирование подтверждено владельцем + REJECTED, // бронирование отклонено владельцем + CANCELED // бронирование отменено создателем +} diff --git a/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java new file mode 100644 index 0000000..247e577 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,117 @@ +package ru.practicum.shareit.client; + +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; + } + + 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(); + } + + 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); + } + + 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; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java new file mode 100644 index 0000000..018d504 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -0,0 +1,56 @@ +package ru.practicum.shareit.item; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.Map; + +@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() + ); + } + + public ResponseEntity createItem(Long ownerId, ItemDto itemDto) { + return post("", ownerId, itemDto); + } + + public ResponseEntity findItemById(Long itemId, Long userId) { + return get("/" + itemId, userId); + } + + public ResponseEntity findAllItems(Long userId) { + return get("", userId); + } + + public ResponseEntity searchItemsByText(Long ownerId, String text) { + Map parameters = Map.of("text", text); + return get("/search?text={text}", ownerId, parameters); + } + + public ResponseEntity updateItem(Long id, Long ownerId, ItemDto itemDto) { + return patch("/" + id, ownerId, itemDto); + } + + public void deleteItem(Long id, Long userId) { + delete("/" + id, userId); + } + + public ResponseEntity addComment(Long itemId, Long userId, CommentDto commentDto) { + return post("/" + itemId + "/comment", userId, commentDto); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java new file mode 100644 index 0000000..21e70c3 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,89 @@ +package ru.practicum.shareit.item; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.validator.ValidAction; + +@Controller +@RequestMapping(path = "/items") +@RequiredArgsConstructor +@Slf4j +@Validated +public class ItemController { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + private final ItemClient itemClient; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createItem( + @RequestHeader(HEADER_USER_ID) final Long ownerId, + @Validated(ValidAction.OnCreate.class) @RequestBody ItemDto itemDto) { + log.info("Пользователь id={} добавляет вещь : {}", ownerId, itemDto.toString()); + return itemClient.createItem(ownerId, itemDto); + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findItem(@PathVariable Long id, + @RequestHeader(HEADER_USER_ID) Long userId) { + log.info("Польдователь id={} просматривает информацию о вещи id={}.", userId, id); + return itemClient.findItemById(id, userId); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findAllItems( + @RequestHeader(HEADER_USER_ID) final Long ownerId) { + log.info("Запрашиваем список вещей владельца id={}", ownerId); + return itemClient.findAllItems(ownerId); + } + + @GetMapping("/search") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity onSearch( + @RequestHeader(HEADER_USER_ID) Long userId, + @RequestParam(name = "text") String text) { + log.info("Ищем вещи содержащие текст \"{}\" в названии или описании.", text); + return itemClient.searchItemsByText(userId, text); + } + + @PatchMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updateItem( + @RequestHeader(HEADER_USER_ID) final Long ownerId, + @PathVariable Long id, + @RequestBody ItemDto itemDto) { + log.info("Пользователь id={} обновляет сведения об элемете id={} {}", ownerId, id, itemDto); + itemDto.setId(id); + return itemClient.updateItem(id, ownerId, itemDto); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public void deleteItem( + @RequestHeader(HEADER_USER_ID) final Long ownerId, + @PathVariable Long id) { + log.info("пользователь id={}. Удаляет вещь id={}", ownerId, id); + itemClient.deleteItem(id, ownerId); + } + + @PostMapping("/{itemId}/comment") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity addComment( + @RequestHeader(HEADER_USER_ID) Long userId, + @PathVariable Long itemId, + @RequestBody CommentDto commentDto) { + log.info("Пользователь id={} добавляет комментарий для вещи id={}", userId, itemId); + commentDto.setAuthorId(userId); + commentDto.setItemId(itemId); + return itemClient.addComment(itemId, userId, commentDto); + } + +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/CommentDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java similarity index 97% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 207dadf..3c384b3 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -30,8 +30,9 @@ public class ItemDto { @NotNull(message = "Доступ должен быть определен.", groups = {ValidAction.OnCreate.class}) private Boolean available; - private Long request; + private Long requestId; private Instant lastBooking; private Instant nextBooking; } + diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java new file mode 100644 index 0000000..565e5fe --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit.request; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +@Service +public class ItemRequestClient extends BaseClient { + private static final String API_PREFIX = "/requests"; + + public ItemRequestClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity createRequest(Long customerId, ItemRequestDto itemRequestDto) { + return post("", customerId, itemRequestDto); + } + + public ResponseEntity findRequest(Long customerId, Long itemId) { + return get("/" + itemId, customerId); + } + + public ResponseEntity findRequestsByCustomerId(Long customerId) { + return get("", customerId); + } + + public ResponseEntity findAllRequests(Long customerId) { + return get("/all", customerId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java new file mode 100644 index 0000000..1bc2bfa --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,71 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +@Controller +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +@Slf4j +@Validated +public class ItemRequestController { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + private final ItemRequestClient itemRequestClient; + + /** + * Создаем новый запрос + * + * @param customerId - идентификатор заказчика + * @param itemRequestDto - объект описания запроса + * @return - сохраненный запрос + * @throws Exception + */ + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createItemRequest( + @RequestHeader(HEADER_USER_ID) final Long customerId, + @RequestBody ItemRequestDto itemRequestDto) throws Exception { + log.info("Пользователь id={} делает заказ : {}", customerId, itemRequestDto.getDescription()); + return itemRequestClient.createRequest(customerId, itemRequestDto); + } + + /** + * Поиск запроса по идентификатору + */ + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findItemRequest( + @RequestHeader(HEADER_USER_ID) Long userId, + @PathVariable Long id) { + log.info("Пользователь id={} просматривает заказ id={}", userId, id); + return itemRequestClient.findRequest(userId, id); + } + + /** + * Поиск пользователем своих запросов + */ + @GetMapping("") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findRequestsByUserId( + @RequestHeader(HEADER_USER_ID) Long userId) { + log.info("Пользователь id={} просматривает свои заказы.", userId); + return itemRequestClient.findRequestsByCustomerId(userId); + } + + /** + * Поиск пользователем всех чужих запросов + */ + @GetMapping("/all") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findAllRequests( + @RequestHeader(HEADER_USER_ID) final Long userId) { + log.info("Пользователь id={} просматривает все заказы.", userId); + return itemRequestClient.findAllRequests(userId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..045e2a5 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.Instant; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemRequestDto { + private Long id; + + @NotBlank(message = "Описание запроса не может быть пустым") + private String description; + + private UserDto requestor; + private Instant created; +} + diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java new file mode 100644 index 0000000..1f86137 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -0,0 +1,44 @@ +package ru.practicum.shareit.user; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.user.dto.UserDto; + +@Service +public class UserClient extends BaseClient { + private static final String API_PREFIX = "/users"; + + public UserClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity addUser(UserDto userDto) { + return post("", userDto); + } + + public ResponseEntity findUserByID(Long userId) { + return get("/" + userId, userId); + } + + public ResponseEntity findAllUsers() { + return get(""); + } + + public ResponseEntity updateUser(Long userId, UserDto userDto) { + return patch("/" + userId, userDto); + } + + public void deleteUser(Long userId) { + delete("/" + userId); + } +} diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java similarity index 62% rename from src/main/java/ru/practicum/shareit/user/UserController.java rename to gateway/src/main/java/ru/practicum/shareit/user/UserController.java index 693a519..56a04d5 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,39 +1,37 @@ package ru.practicum.shareit.user; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.user.dto.UserDto; -import ru.practicum.shareit.user.service.UserService; import ru.practicum.shareit.validator.ValidAction; -import java.util.Collection; - /** * Класс обработки http запросов о пользователях. */ -@Slf4j -@RestController +@Controller @RequestMapping(path = "/users") +@RequiredArgsConstructor +@Slf4j +@Validated public class UserController { - - private final UserService userService; - - public UserController(UserService userService) { - this.userService = userService; - } + private final UserClient userClient; /** - * Метод поиска всех пользователей + * Метод добавления нового пользователя. * - * @return - список пользователей + * @param userDto - объект для добавления + * @return - подтверждение добавленного объекта */ - @GetMapping - @ResponseStatus(HttpStatus.OK) - public Collection findAllUser() { - log.info("Запрашиваем список всех пользователей."); - return userService.getAllUsers(); + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity addNewUser(@Validated(ValidAction.OnCreate.class) @RequestBody UserDto userDto) { + log.info("Создаем пользователя : {}.", userDto.toString()); + return userClient.addUser(userDto); } /** @@ -44,49 +42,36 @@ public Collection findAllUser() { */ @GetMapping("/{id}") @ResponseStatus(HttpStatus.OK) - public UserDto findUser(@PathVariable Long id) { + public ResponseEntity findUser(@PathVariable Long id) { log.info("Ищем пользователя id={}.", id); - return userService.getUserById(id); + return userClient.findUserByID(id); } /** - * Метод добавления нового пользователя. + * Метод поиска всех пользователей * - * @param userDto - объект для добавления - * @return - подтверждение добавленного объекта + * @return - список пользователей */ - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public UserDto addNewUser(@Validated(ValidAction.OnCreate.class) @RequestBody UserDto userDto) { - log.info("Создаем пользователя : {}.", userDto.toString()); - return userService.createUser(userDto); + @GetMapping + @ResponseStatus(HttpStatus.OK) + public ResponseEntity findAllUser() { + log.info("Запрашиваем список всех пользователей."); + return userClient.findAllUsers(); } /** * Метод обновления информации о пользователе. * - * @param updUser - объект с обновленной информацией о пользователе + * @param updUserDto - объект с обновленной информацией о пользователе * @return - подтверждение обновленного объекта */ @PatchMapping("/{id}") @ResponseStatus(HttpStatus.OK) - public UserDto updateUser(@PathVariable Long id, - @Validated(ValidAction.OnUpdate.class) @RequestBody UserDto updUser) { - updUser.setId(id); - log.info("Обновляем данные о пользователе : {}", updUser); - return userService.updateUser(updUser); - } - - /** - * Удаление всех пользователей - * - * @return - сообщение о выполнении - */ - @DeleteMapping - @ResponseStatus(HttpStatus.OK) - public Collection deleteAllUsers() { - log.info("Удаляем всех пользователей."); - return userService.deleteAllUsers(); + public ResponseEntity updateUser(@PathVariable Long id, + @Validated(ValidAction.OnUpdate.class) @RequestBody UserDto updUserDto) { + updUserDto.setId(id); + log.info("Обновляем данные о пользователе : {}", updUserDto); + return userClient.updateUser(id, updUserDto); } /** @@ -98,7 +83,7 @@ public Collection deleteAllUsers() { @ResponseStatus(HttpStatus.OK) public void deleteUser(@PathVariable Long id) { log.info("Удаляем пользователя id={}.", id); - userService.deleteUser(id); + userClient.deleteUser(id); } - } + diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserDto.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java diff --git a/gateway/src/main/java/ru/practicum/shareit/validator/ValidAction.java b/gateway/src/main/java/ru/practicum/shareit/validator/ValidAction.java new file mode 100644 index 0000000..ff5aae4 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/validator/ValidAction.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.validator; + +public interface ValidAction { + + interface OnCreate { + } + + interface OnUpdate { + } +} \ No newline at end of file diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..9df4b1d --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,5 @@ +logging.level.org.springframework.web.client.RestTemplate=DEBUG +#logging.level.org.apache.http=DEBUG +#logging.level.httpclient.wire=DEBUG +server.port=8080 +shareit-server.url=http://localhost:9090 \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java new file mode 100644 index 0000000..915cec1 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -0,0 +1,137 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.booking.dto.BookItemRequestDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.BookingStatus; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = BookingController.class) +class BookingControllerTest { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + @Autowired + ObjectMapper mapper; + + @MockBean + private BookingClient bookingClient; + + @Autowired + private MockMvc mvc; + + private BookItemRequestDto bookItemRequestDto = new BookItemRequestDto( + 1L, + LocalDateTime.of(2025, 04, 12, 10, 10), + LocalDateTime.of(2025, 04, 15, 10, 10) + ); + + private BookingDto testBookingDto = new BookingDto( + 1L, + LocalDateTime.of(2025, 04, 12, 10, 10), + LocalDateTime.of(2025, 04, 15, 10, 10), + 1L, + new ItemDto(1L, "Iteem", "Description", true, null, null, null), + 2L, + new UserDto(2L, "User", "user@booking.controller.test"), + BookingStatus.WAITING); + + @Test + void getBookings() throws Exception { + List bookingDtos = List.of(testBookingDto); + ResponseEntity response = new ResponseEntity<>( + bookingDtos, HttpStatus.OK); + when(bookingClient.getBookings(anyLong(), any(), anyInt(), anyInt())) + .thenReturn(response); + + mvc.perform(get("/bookings?state=ALL&from=0&size=10") + .header(HEADER_USER_ID, 2L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void bookItem() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testBookingDto, HttpStatus.CREATED); + when(bookingClient.bookItem(anyLong(), any())) + .thenReturn(response); + mvc.perform(post("/bookings") + .header(HEADER_USER_ID, 2L) + .content(mapper.writeValueAsString(bookItemRequestDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.itemId", is((int) bookItemRequestDto.getItemId()))); + } + + @Test + void editBooking() throws Exception { + testBookingDto.setStatus(BookingStatus.APPROVED); + ResponseEntity response = new ResponseEntity<>( + testBookingDto, HttpStatus.OK); + when(bookingClient.approvedBooking(anyLong(), anyLong(), anyBoolean())) + .thenReturn(response); + + mvc.perform(patch("/bookings/1?approved=true") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status", is("APPROVED"))); + } + + @Test + void getBooking() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testBookingDto, HttpStatus.OK); + when(bookingClient.getBooking(anyLong(), anyLong())) + .thenReturn(response); + + mvc.perform(get("/bookings/1") + .header(HEADER_USER_ID, 2L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void findBookingByOwner() throws Exception { + List bookingDtos = List.of(testBookingDto); + ResponseEntity response = new ResponseEntity<>( + bookingDtos, HttpStatus.OK); + when(bookingClient.findBookingsByOwner(anyLong(), any(), anyInt(), anyInt())) + .thenReturn(response); + + mvc.perform(get("/bookings/owner?state=ALL&from=0&size=10") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java new file mode 100644 index 0000000..b480a39 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -0,0 +1,152 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = ItemController.class) +class ItemControllerTest { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + @Autowired + ObjectMapper mapper; + ItemDto testItemDto = new ItemDto( + 1L, + "Item1", + "Item1", + true, + null, + null, + null); + @MockBean + private ItemClient itemClient; + @Autowired + private MockMvc mvc; + + @Test + void createItem() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testItemDto, HttpStatus.CREATED); + when(itemClient.createItem(anyLong(), any())) + .thenReturn(response); + + mvc.perform(post("/items") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(testItemDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.name", is(testItemDto.getName()))); + + + } + + @Test + void findItem() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testItemDto, HttpStatus.OK); + when(itemClient.findItemById(anyLong(), anyLong())) + .thenReturn(response); + + mvc.perform(get("/items/1") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name", is(testItemDto.getName()))); + } + + @Test + void findAllItems() throws Exception { + List itemDtos = new ArrayList<>(); + itemDtos.add(testItemDto); + ResponseEntity response = new ResponseEntity<>( + itemDtos, HttpStatus.OK); + when(itemClient.findAllItems(anyLong())) + .thenReturn(response); + + mvc.perform(get("/items") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + } + + @Test + void onSearch() throws Exception { + List itemDtos = new ArrayList<>(); + itemDtos.add(testItemDto); + ResponseEntity response = new ResponseEntity<>( + itemDtos, HttpStatus.OK); + when(itemClient.searchItemsByText(anyLong(), anyString())) + .thenReturn(response); + + mvc.perform(get("/items/search?text=Item") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + } + + @Test + void updateItem() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testItemDto, HttpStatus.OK); + when(itemClient.updateItem(anyLong(), anyLong(), any())) + .thenReturn(response); + + mvc.perform(patch("/items/1") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(testItemDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name", is(testItemDto.getName()))); + } + + @Test + void addComment() throws Exception { + CommentDto commentDto = new CommentDto(); + commentDto.setItemId(testItemDto.getId()); + commentDto.setText("Comment text"); + + ResponseEntity response = new ResponseEntity<>( + testItemDto, HttpStatus.CREATED); + when(itemClient.addComment(anyLong(), anyLong(), any())) + .thenReturn(response); + + mvc.perform(post("/items/1/comment") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(commentDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java new file mode 100644 index 0000000..0677565 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java @@ -0,0 +1,111 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = ItemRequestController.class) +class ItemRequestControllerTest { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + @Autowired + ObjectMapper mapper; + ItemRequestDto itemRequestDto = new ItemRequestDto( + 1L, + "Request text.", + new UserDto(1L, "user", "user@request.test"), + Instant.now()); + @MockBean + private ItemRequestClient itemRequestClient; + @Autowired + private MockMvc mvc; + + @Test + void createItemRequest() throws Exception { + ResponseEntity response = new ResponseEntity<>( + itemRequestDto, HttpStatus.CREATED); + when(itemRequestClient.createRequest(anyLong(), any())) + .thenReturn(response); + + mvc.perform(post("/requests") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(itemRequestDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.description", is(itemRequestDto.getDescription()))); + } + + @Test + void findItemRequest() throws Exception { + ResponseEntity response = new ResponseEntity<>( + itemRequestDto, HttpStatus.OK); + when(itemRequestClient.findRequest(anyLong(), anyLong())) + .thenReturn(response); + + mvc.perform(get("/requests/1") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description", is(itemRequestDto.getDescription()))); + } + + @Test + void findRequestsByUserId() throws Exception { + ResponseEntity response = new ResponseEntity<>( + itemRequestDto, HttpStatus.OK); + when(itemRequestClient.findRequestsByCustomerId(anyLong())) + .thenReturn(response); + + mvc.perform(get("/requests") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description", is(itemRequestDto.getDescription()))); + } + + @Test + void findAllRequests() throws Exception { + List itemRequestDtos = new ArrayList<>(); + itemRequestDtos.add(itemRequestDto); + + ResponseEntity response = new ResponseEntity<>( + itemRequestDtos, HttpStatus.OK); + when(itemRequestClient.findAllRequests(anyLong())) + .thenReturn(response); + + mvc.perform(get("/requests/all") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java new file mode 100644 index 0000000..b2bf65d --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,116 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.user.dto.UserDto; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = UserController.class) +class UserControllerTest { + @Autowired + ObjectMapper mapper; + UserDto testUserDto = new UserDto( + 1L, + "tetUser", + "test_user@gateway.test"); + @MockBean + private UserClient userClient; + @Autowired + private MockMvc mvc; + + @Test + void addNewUser() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testUserDto, HttpStatus.CREATED); + + when(userClient.addUser(any())) + .thenReturn(response); + + mvc.perform(post("/users") + .content(mapper.writeValueAsString(testUserDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(testUserDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(testUserDto.getName()))) + .andExpect(jsonPath("$.email", is(testUserDto.getEmail()))); + } + + @Test + void findUser() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testUserDto, HttpStatus.OK); + + when(userClient.findUserByID(anyLong())) + .thenReturn(response); + + mvc.perform(get("/users/1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(testUserDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(testUserDto.getName()))) + .andExpect(jsonPath("$.email", is(testUserDto.getEmail()))); + } + + @Test + void findAllUser() throws Exception { + List userDtos = new ArrayList<>(); + userDtos.add(testUserDto); + + ResponseEntity response = new ResponseEntity<>( + userDtos, HttpStatus.OK); + + when(userClient.findAllUsers()) + .thenReturn(response); + + mvc.perform(get("/users") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(1)); + + } + + @Test + void updateUser() throws Exception { + ResponseEntity response = new ResponseEntity<>( + testUserDto, HttpStatus.OK); + + when(userClient.updateUser(anyLong(), any())) + .thenReturn(response); + + mvc.perform(patch("/users/1") + .content(mapper.writeValueAsString(testUserDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(testUserDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(testUserDto.getName()))) + .andExpect(jsonPath("$.email", is(testUserDto.getEmail()))); + } + +} \ No newline at end of file diff --git a/gateway/src/test/resources/application.properties b/gateway/src/test/resources/application.properties new file mode 100644 index 0000000..9df4b1d --- /dev/null +++ b/gateway/src/test/resources/application.properties @@ -0,0 +1,5 @@ +logging.level.org.springframework.web.client.RestTemplate=DEBUG +#logging.level.org.apache.http=DEBUG +#logging.level.httpclient.wire=DEBUG +server.port=8080 +shareit-server.url=http://localhost:9090 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7585826..5acfe0b 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ ru.practicum shareit + pom 0.0.1-SNAPSHOT ShareIt @@ -19,88 +20,29 @@ 21 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.postgresql - postgresql - runtime - - - - org.projectlombok - lombok - true - - - - com.h2database - h2 - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-validation - - - - org.zalando - logbook-spring-boot-starter - 3.7.2 - - - - org.postgresql - postgresql - - - - org.springframework.boot - spring-boot-starter-data-jpa - - + + server + gateway + - - - src/main/resources - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + + org.projectlombok + lombok + + + + org.apache.maven.plugins maven-surefire-plugin @@ -247,17 +189,5 @@ - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - - - - + \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=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..a818223 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-server + 0.0.1-SNAPSHOT + + ShareIt Server + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.postgresql + postgresql + runtime + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-validation + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + + + \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItServer.java similarity index 71% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItServer.java index a10a87d..ddf75ea 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/server/src/main/java/ru/practicum/shareit/ShareItServer.java @@ -4,10 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ShareItApp { - +public class ShareItServer { public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); + SpringApplication.run(ShareItServer.class, args); } - -} +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java b/server/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java new file mode 100644 index 0000000..985d4f0 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java @@ -0,0 +1,53 @@ +package ru.practicum.shareit.advisor; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.practicum.shareit.excepton.AccessDeniedException; +import ru.practicum.shareit.excepton.InternalServerException; +import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.excepton.ValidationException; + +/** + * Класс обработки исключений при обработке поступивших http запросов + */ +@Slf4j +@RestControllerAdvice +public class ErrorAdvisor { + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorMessage onValidationException(ValidationException exception) { + log.error("400 {}.", exception.getMessage()); + return new ErrorMessage(exception.getMessage()); + } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorMessage notFoundObject(NotFoundException exception) { + log.error("404 {}.", exception.getMessage()); + return new ErrorMessage(exception.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorMessage onInternalException(final InternalServerException e) { + log.error("500 {}", e.getMessage()); + return new ErrorMessage(e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorMessage onAccessDeniedException(final AccessDeniedException e) { + log.error("403 {}", e.getMessage()); + return new ErrorMessage(e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorMessage handleException(final Exception e) { + log.error("Error", e); + return new ErrorMessage(e.getMessage()); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/advisor/ErrorMessage.java b/server/src/main/java/ru/practicum/shareit/advisor/ErrorMessage.java new file mode 100644 index 0000000..59e7e9b --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/advisor/ErrorMessage.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.advisor; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Класс сообщения об ошибке выполнения запроса + */ +@Getter +@AllArgsConstructor +public class ErrorMessage { + private String error; +} diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/server/src/main/java/ru/practicum/shareit/booking/Booking.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/Booking.java rename to server/src/main/java/ru/practicum/shareit/booking/Booking.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java similarity index 80% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingController.java index 0588ee6..cafddb1 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -2,12 +2,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.enums.SearchState; import ru.practicum.shareit.booking.service.BookingService; -import ru.practicum.shareit.validator.ValidAction; import java.util.List; @@ -29,7 +27,6 @@ public BookingController(BookingService bookingService) { @ResponseStatus(HttpStatus.CREATED) public BookingDto addBooking( @RequestHeader(HEADER_USER_ID) final Long bookerId, - @Validated(ValidAction.OnCreate.class) @RequestBody BookingDto bookingDto) { log.info("Пользователь id={} cоздает запрос на бронирование : {}", bookerId, bookingDto); return bookingService.addBooking(bookingDto, bookerId); @@ -38,7 +35,7 @@ public BookingDto addBooking( @PatchMapping("/{id}") @ResponseStatus(HttpStatus.OK) public BookingDto editBooking( - @RequestHeader(HEADER_USER_ID) final Long editorId, + @RequestHeader(HEADER_USER_ID) Long editorId, @PathVariable Long id, @RequestParam Boolean approved) { log.info("Пользователь id={} редактирует запрос на бронирование id={}", editorId, id); @@ -58,7 +55,10 @@ public BookingDto findBooking( @ResponseStatus(HttpStatus.OK) public List findBookingByBooker( @RequestHeader(HEADER_USER_ID) final Long bookerId, - @RequestParam(defaultValue = "ALL") SearchState state) { + @RequestParam(name = "state", defaultValue = "all") String stateParam) { + SearchState state = SearchState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Неизвестный режим: " + stateParam)); + log.info("Пользователь id={} просматривает свои запроcы на бронирование", bookerId); return bookingService.findBookingByBooker(bookerId, state); } @@ -67,8 +67,11 @@ public List findBookingByBooker( @ResponseStatus(HttpStatus.OK) public List findBookingByOwner( @RequestHeader(HEADER_USER_ID) final Long ownerId, - @RequestParam(defaultValue = "ALL") SearchState state) { - log.info("Пользователь id={} просматривает запроcы на вои вещи", ownerId); + @RequestParam(name = "state", defaultValue = "all") String stateParam) { + SearchState state = SearchState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Неизвестный режим: " + stateParam)); + + log.info("Пользователь id={} просматривает запроcы на все вещи", ownerId); return bookingService.findBookingsByOwner(ownerId, state); } diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java new file mode 100644 index 0000000..8f79c91 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.booking.enums.BookingStatus; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +/** + * DTO класс бронирования + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BookingDto { + private Long id; + private LocalDateTime start; + private LocalDateTime end; + private Long itemId; + private ItemDto item; + private Long bookerId; + private UserDto booker; + private BookingStatus status = BookingStatus.WAITING; +} diff --git a/src/main/java/ru/practicum/shareit/booking/enums/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/enums/BookingStatus.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/enums/BookingStatus.java rename to server/src/main/java/ru/practicum/shareit/booking/enums/BookingStatus.java diff --git a/server/src/main/java/ru/practicum/shareit/booking/enums/SearchState.java b/server/src/main/java/ru/practicum/shareit/booking/enums/SearchState.java new file mode 100644 index 0000000..20690e6 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/enums/SearchState.java @@ -0,0 +1,30 @@ +package ru.practicum.shareit.booking.enums; + +import java.util.Optional; + +/** + * Описание режимов поиска запросов на бронирование + */ +public enum SearchState { + // Все + ALL, + // Текущие + CURRENT, + // Будущие + FUTURE, + // Завершенные + PAST, + // Отклоненные + REJECTED, + // Ожидающие подтверждения + WAITING; + + public static Optional from(String stringState) { + for (SearchState state : values()) { + if (state.name().equalsIgnoreCase(stringState)) { + return Optional.of(state); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/service/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java similarity index 85% rename from src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java index 71d684e..6bbc7fa 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -45,13 +45,10 @@ public BookingDto addBooking(BookingDto bookingDto, Long userId) { throw new ValidationException("Отсутствуют сведения о заказчике."); } User user = userRepository.findById(userId) - .orElseThrow(() -> - new NotFoundException("Не найден пользователь id=" + userId)); + .orElseThrow(() -> new NotFoundException("Не найден пользователь id=" + userId)); Item item = itemRepository.findById(bookingDto.getItemId()) - .orElseThrow(() -> - new NotFoundException("Не найдена вещ id=" + - bookingDto.getItemId())); + .orElseThrow(() -> new NotFoundException("Не найдена вещ id=" + bookingDto.getItemId())); if (!item.getAvailable()) { throw new ValidationException(ItemMapper.toItemDto(item) + " Недоступна для бронирования."); } @@ -64,16 +61,14 @@ public BookingDto addBooking(BookingDto bookingDto, Long userId) { Booking savedBooking = bookingRepository.save(booking); Long bookingId = savedBooking.getId(); savedBooking = bookingRepository.findById(bookingId) - .orElseThrow(() -> - new InternalServerException("Ошибка при добавлении запроса на бронирование.")); + .orElseThrow(() -> new InternalServerException("Ошибка при добавлении запроса на бронирование.")); return BookingMapper.toBookingDto(savedBooking); } @Override public BookingDto findBookingById(Long bookingId, Long userId) { Booking booking = bookingRepository.findById(bookingId) - .orElseThrow(() -> - new NotFoundException("Не найден запрос на бронирование id=" + bookingId)); + .orElseThrow(() -> new NotFoundException("Не найден запрос на бронирование id=" + bookingId)); if (!booking.getBooker().getId().equals(userId) && !booking.getItem().getOwner().getId().equals(userId)) { throw new AccessDeniedException("Пользователь id=" + userId + " не является автором запроса или хозяином вещи."); @@ -84,8 +79,7 @@ public BookingDto findBookingById(Long bookingId, Long userId) { @Override public BookingDto approvedBooking(Long id, Long editorId, Boolean approved) { Booking booking = bookingRepository.findById(id) - .orElseThrow(() -> - new NotFoundException("Не найден запрос id=" + id)); + .orElseThrow(() -> new NotFoundException("Не найден запрос id=" + id)); if (!booking.getItem().getOwner().getId().equals(editorId)) { throw new ValidationException("Пользователь id=" + editorId + " не является хозяином."); } @@ -101,8 +95,7 @@ public BookingDto approvedBooking(Long id, Long editorId, Boolean approved) { @Override public List findBookingsByOwner(Long ownerId, SearchState state) { userRepository.findById(ownerId) - .orElseThrow(() -> - new NotFoundException("Не найден пользователь id=" + ownerId)); + .orElseThrow(() -> new NotFoundException("Не найден пользователь id=" + ownerId)); List bookings = new ArrayList<>(); if (state.equals(SearchState.ALL)) { bookings = bookingRepository.findAllByItem_OwnerIdOrderByStartDesc(ownerId); @@ -125,7 +118,6 @@ public List findBookingByBooker(Long bookerId, SearchState state) { userRepository.findById(bookerId) .orElseThrow(() -> new NotFoundException("Не найден пользователь id=" + bookerId)); - List bookings = new ArrayList<>(); if (state.equals(SearchState.REJECTED)) { bookings = bookingRepository.findBookingsByBookerAndStatus(bookerId, BookingStatus.REJECTED); @@ -134,11 +126,11 @@ public List findBookingByBooker(Long bookerId, SearchState state) { } else if (state.equals(SearchState.ALL)) { bookings = bookingRepository.findAllByBookerIdOrderByStartDesc(bookerId); } else if (state.equals(SearchState.PAST)) { - bookings = bookingRepository.findByBooker_IdAndEndIsBefore(bookerId, Instant.now(), Sort.by("DESC", "start")); + bookings = bookingRepository.findByBooker_IdAndEndIsBefore(bookerId, Instant.now(), Sort.by("start").descending()); } else if (state.equals(SearchState.CURRENT)) { bookings = bookingRepository.findBookingsByBookerIdCurrent(bookerId, Instant.now()); } else if (state.equals(SearchState.FUTURE)) { - bookings = bookingRepository.findByBooker_IdAndStartIsAfter(bookerId, Instant.now(), Sort.by("DESC", "start")); + bookings = bookingRepository.findByBooker_IdAndStartIsAfter(bookerId, Instant.now(), Sort.by("start").descending()); } return bookings.stream().map(BookingMapper::toBookingDto).toList(); } diff --git a/src/main/java/ru/practicum/shareit/excepton/AccessDeniedException.java b/server/src/main/java/ru/practicum/shareit/excepton/AccessDeniedException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/excepton/AccessDeniedException.java rename to server/src/main/java/ru/practicum/shareit/excepton/AccessDeniedException.java diff --git a/src/main/java/ru/practicum/shareit/excepton/InternalServerException.java b/server/src/main/java/ru/practicum/shareit/excepton/InternalServerException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/excepton/InternalServerException.java rename to server/src/main/java/ru/practicum/shareit/excepton/InternalServerException.java diff --git a/src/main/java/ru/practicum/shareit/excepton/NotFoundException.java b/server/src/main/java/ru/practicum/shareit/excepton/NotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/excepton/NotFoundException.java rename to server/src/main/java/ru/practicum/shareit/excepton/NotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/excepton/ValidationException.java b/server/src/main/java/ru/practicum/shareit/excepton/ValidationException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/excepton/ValidationException.java rename to server/src/main/java/ru/practicum/shareit/excepton/ValidationException.java diff --git a/src/main/java/ru/practicum/shareit/item/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/CommentMapper.java rename to server/src/main/java/ru/practicum/shareit/item/CommentMapper.java diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemController.java similarity index 83% rename from src/main/java/ru/practicum/shareit/item/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/ItemController.java index 6030877..758ac97 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -2,24 +2,24 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemCommentsDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.service.CommentService; import ru.practicum.shareit.item.service.ItemService; -import ru.practicum.shareit.validator.ValidAction; import java.util.Collection; /** - * TODO Sprint add-controllers. + * Класс обработки запросов на работу с вешью */ @Slf4j @RestController @RequestMapping("/items") public class ItemController { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + private final ItemService itemService; private final CommentService commentService; @@ -31,7 +31,7 @@ public ItemController(ItemService itemService, CommentService commentService) { @GetMapping @ResponseStatus(HttpStatus.OK) public Collection findAllItems( - @RequestHeader("X-Sharer-User-Id") final Long ownerId) { + @RequestHeader(HEADER_USER_ID) final Long ownerId) { log.info("Запрашиваем список вещей владельца id={}", ownerId); return itemService.getItemsByOwnerId(ownerId); } @@ -39,7 +39,7 @@ public Collection findAllItems( @GetMapping("/{id}") @ResponseStatus(HttpStatus.OK) public ItemCommentsDto findItem(@PathVariable Long id, - @RequestHeader("X-Sharer-User-Id") final Long userId) { + @RequestHeader(HEADER_USER_ID) final Long userId) { log.info("Польдователь id={} просматривает информацию о вещи id={}.", userId, id); return itemService.getItem(id, userId); } @@ -54,8 +54,8 @@ public Collection onSearch(@RequestParam String text) { @PostMapping @ResponseStatus(HttpStatus.CREATED) public ItemDto createItem( - @RequestHeader("X-Sharer-User-Id") final Long ownerId, - @Validated(ValidAction.OnCreate.class) @RequestBody ItemDto itemDto) { + @RequestHeader(HEADER_USER_ID) final Long ownerId, + @RequestBody ItemDto itemDto) { log.info("Пользователь id={} добавляет вещь : {}", ownerId, itemDto.toString()); return itemService.addItem(itemDto, ownerId); } @@ -63,7 +63,7 @@ public ItemDto createItem( @PatchMapping("/{id}") @ResponseStatus(HttpStatus.OK) public ItemDto updateItem( - @RequestHeader("X-Sharer-User-Id") final Long ownerId, + @RequestHeader(HEADER_USER_ID) final Long ownerId, @PathVariable Long id, @RequestBody ItemDto itemDto) { log.info("Пользователь id={} обновляет сведения об элемете id={} {}", ownerId, id, itemDto); @@ -74,7 +74,7 @@ public ItemDto updateItem( @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.OK) public void deleteItem( - @RequestHeader("X-Sharer-User-Id") final Long ownerId, + @RequestHeader(HEADER_USER_ID) final Long ownerId, @PathVariable Long id) { log.info("пользователь id={}. Удаляет вещь id={}", ownerId, id); itemService.deleteItem(id, ownerId); @@ -90,7 +90,7 @@ public String deleteAllItems() { @PostMapping("/{itemId}/comment") @ResponseStatus(HttpStatus.CREATED) public CommentDto addComment( - @RequestHeader("X-Sharer-User-Id") final Long userId, + @RequestHeader(HEADER_USER_ID) final Long userId, @PathVariable Long itemId, @RequestBody CommentDto commentDto) { log.info("Пользователь id={} добавляет комментарий для вещи id={}", userId, itemId); @@ -98,5 +98,4 @@ public CommentDto addComment( commentDto.setItemId(itemId); return commentService.addComment(commentDto); } - } diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java similarity index 59% rename from src/main/java/ru/practicum/shareit/item/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/ItemMapper.java index 71039d4..c1915fc 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -2,7 +2,9 @@ import ru.practicum.shareit.item.dto.ItemCommentsDto; import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemShortDto; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.ItemRequestMapper; import java.util.List; @@ -15,12 +17,10 @@ public static ItemDto toItemDto(Item item) { itemDto.setId(item.getId()); itemDto.setName(item.getName()); itemDto.setDescription(item.getDescription()); - itemDto.setAvailable(item.getAvailable()); if (item.getRequest() != null) { - itemDto.setRequest(item.getRequest().getId()); - } else { - itemDto.setRequest(null); + itemDto.setRequestId(item.getRequest().getId()); } + itemDto.setAvailable(item.getAvailable()); return itemDto; } @@ -31,23 +31,26 @@ public static ItemCommentsDto toItemCommentsDto(Item item) { itemDto.setDescription(item.getDescription()); itemDto.setAvailable(item.getAvailable()); if (item.getRequest() != null) { - itemDto.setRequest(item.getRequest().getId()); - } else { - itemDto.setRequest(null); + itemDto.setRequest(ItemRequestMapper.toItemRequestDto(item.getRequest())); } itemDto.setComments(List.of()); return itemDto; - } public static Item toItem(ItemDto itemDto) { - return new Item( - itemDto.getId(), - itemDto.getName(), - itemDto.getDescription(), - itemDto.getAvailable(), - null - ); + Item item = new Item(); + item.setId(itemDto.getId()); + item.setName(itemDto.getName()); + item.setDescription(itemDto.getDescription()); + item.setAvailable(itemDto.getAvailable()); + return item; } + public static ItemShortDto toItemShortDto(Item item) { + ItemShortDto itemDto = new ItemShortDto(); + itemDto.setId(item.getId()); + itemDto.setName(item.getName()); + itemDto.setOwnerId(item.getOwner().getId()); + return itemDto; + } } diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..11d58d6 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CommentDto { + private Long id; + private String text; + private Long authorId; + private String authorName; + private Long itemId; + private Instant created; +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemCommentsDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemCommentsDto.java similarity index 83% rename from src/main/java/ru/practicum/shareit/item/dto/ItemCommentsDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemCommentsDto.java index 5113d17..40d3a2d 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemCommentsDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemCommentsDto.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import ru.practicum.shareit.request.dto.ItemRequestDto; import java.time.Instant; import java.util.List; @@ -16,7 +17,7 @@ public class ItemCommentsDto { private String name; private String description; private Boolean available; - private Long request; + private ItemRequestDto request; private Instant lastBooking; private Instant nextBooking; } diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java new file mode 100644 index 0000000..520ece3 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +/** + * Класс описания представления объектов при обмене данными + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemDto { + private Long id; + private String name; + private String description; + private Boolean available; + private Long requestId; + private Instant lastBooking; + private Instant nextBooking; +} diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemShortDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemShortDto.java new file mode 100644 index 0000000..e2858e5 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemShortDto.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemShortDto { + private Long id; + private String name; + private Long ownerId; +} diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java similarity index 99% rename from src/main/java/ru/practicum/shareit/item/model/Comment.java rename to server/src/main/java/ru/practicum/shareit/item/model/Comment.java index 1e239db..56b050f 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Comment.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java @@ -19,18 +19,14 @@ public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "text", nullable = false) private String text; - @ManyToOne @JoinColumn(name = "item_id") private Item item; - @ManyToOne @JoinColumn(name = "author_id") private User author; - @Column(name = "created", nullable = false) private Instant created; } diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/server/src/main/java/ru/practicum/shareit/item/model/Item.java similarity index 99% rename from src/main/java/ru/practicum/shareit/item/model/Item.java rename to server/src/main/java/ru/practicum/shareit/item/model/Item.java index 46c3ab7..eb6a3fd 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -28,19 +28,15 @@ public class Item { @NotBlank(message = "Имя не может быть пустым", groups = {ValidAction.OnCreate.class}) @Column(name = "name", nullable = false) private String name; - @Size(max = 255, message = "Максимальная длина описания - 255 символов.", groups = {ValidAction.OnCreate.class, ValidAction.OnUpdate.class}) @Column(name = "description") private String description; - @ManyToOne @JoinColumn(name = "owner_id") private User owner; - @Column(name = "available") private Boolean available; - @ManyToOne @JoinColumn(name = "request_id") private ItemRequest request; diff --git a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java similarity index 83% rename from src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java rename to server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java index d6cf0d1..8d64273 100644 --- a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java +++ b/server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java @@ -13,4 +13,8 @@ public interface ItemRepository extends JpaRepository { "WHERE (UPPER(it.name) LIKE UPPER(?1) OR UPPER(it.description) LIKE UPPER(?1))" + "AND it.available = TRUE") List findByNameOrDescriptionContainingIgnoreCase(String textSearch); + + List findAllByRequest_IdEquals(Long requestId); + + List findAllByRequest_IdIn(List requestIds); } diff --git a/src/main/java/ru/practicum/shareit/item/service/CommentService.java b/server/src/main/java/ru/practicum/shareit/item/service/CommentService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/service/CommentService.java rename to server/src/main/java/ru/practicum/shareit/item/service/CommentService.java diff --git a/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java similarity index 86% rename from src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java index 3e26084..2e4273e 100644 --- a/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/service/CommentServiceImpl.java @@ -19,6 +19,7 @@ import java.time.ZoneOffset; @Service +@Transactional(readOnly = true) public class CommentServiceImpl implements CommentService { private final CommentRepository commentRepository; private final BookingRepository bookingRepository; @@ -38,34 +39,25 @@ public CommentDto addComment(CommentDto commentDto) { Long userId = commentDto.getAuthorId(); User user = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("Пользователь не найден id=" + userId)); - Long itemId = commentDto.getItemId(); Booking booking = bookingRepository.findBookingsByBookerIdAndItemId(userId, itemId); if (booking == null) { - throw new NotFoundException("Пользователь id=" + userId + - " не бронировал вещь id=" + itemId); + throw new NotFoundException("Пользователь id=" + userId + " не бронировал вещь id=" + itemId); } - LocalDateTime now = LocalDateTime.now(); if (LocalDateTime.ofInstant(booking.getEnd(), ZoneOffset.UTC).isAfter(now)) { - throw new ValidationException("Комментарий возможен после завершения срока аренды. " + - "Конец аренды : " + LocalDateTime.ofInstant(booking.getEnd(), ZoneOffset.UTC) + - " Текущее время : " + now); + throw new ValidationException("Комментировать можно после завершения срока аренды. " + LocalDateTime.ofInstant(booking.getEnd(), ZoneOffset.UTC)); } - Item item = booking.getItem(); if (item.getOwner().getId().equals(userId)) { throw new ValidationException("Хозяину нельзя оставлять комментарий."); } - Comment comment = new Comment(); comment.setAuthor(user); comment.setItem(item); comment.setText(commentDto.getText()); comment.setCreated(Instant.now()); - Comment savedComment = commentRepository.save(comment); return CommentMapper.toDto(savedComment); } - } diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/service/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemService.java diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java similarity index 82% rename from src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java index 1a15c03..b1ad790 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -5,6 +5,7 @@ import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.excepton.AccessDeniedException; import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.excepton.ValidationException; import ru.practicum.shareit.item.CommentMapper; import ru.practicum.shareit.item.ItemMapper; import ru.practicum.shareit.item.dto.CommentDto; @@ -13,6 +14,8 @@ import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.item.repository.CommentRepository; import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; import ru.practicum.shareit.user.User; import ru.practicum.shareit.user.repository.UserRepository; @@ -26,15 +29,18 @@ public class ItemServiceImpl implements ItemService { private final UserRepository userRepository; private final BookingRepository bookingRepository; private final CommentRepository commentRepository; + private final ItemRequestRepository requestRepository; public ItemServiceImpl(ItemRepository itemRepository, UserRepository userRepository, BookingRepository bookingRepository, - CommentRepository commentRepository) { + CommentRepository commentRepository, + ItemRequestRepository requestRepository) { this.itemRepository = itemRepository; this.userRepository = userRepository; this.bookingRepository = bookingRepository; this.commentRepository = commentRepository; + this.requestRepository = requestRepository; } @Override @@ -44,10 +50,19 @@ public ItemDto addItem(ItemDto itemDto, Long ownerId) { Item newItem = ItemMapper.toItem(itemDto); newItem.setOwner(owner); + Long requestId = itemDto.getRequestId(); + if (requestId != null) { + ItemRequest request = requestRepository.findById(requestId) + .orElseThrow(() -> new ValidationException("Не найден запрос id=" + requestId)); + newItem.setRequest(request); + } else { + newItem.setRequest(null); + } Item savedItem = itemRepository.save(newItem); return ItemMapper.toItemDto(savedItem); } + @Override public ItemDto updateItem(ItemDto updItemDto, Long ownerId) { User owner = userRepository.findById(ownerId) @@ -62,6 +77,14 @@ public ItemDto updateItem(ItemDto updItemDto, Long ownerId) { if (!updItem.getOwner().equals(item.getOwner())) { throw new AccessDeniedException("Редактировать данные может только владелец вещи."); } + Long requestId = updItemDto.getRequestId(); + if (requestId != null) { + ItemRequest request = requestRepository.findById(requestId) + .orElseThrow(() -> new ValidationException("Не найден запрос id=" + requestId)); + item.setRequest(request); + } else { + item.setRequest(null); + } if (updItem.getName() != null) { item.setName(updItem.getName()); @@ -69,9 +92,6 @@ public ItemDto updateItem(ItemDto updItemDto, Long ownerId) { if (updItem.getDescription() != null) { item.setDescription(updItem.getDescription()); } - if (updItem.getRequest() != null) { - item.setRequest(updItem.getRequest()); - } if (updItem.getAvailable() != null) { item.setAvailable(updItem.getAvailable()); } @@ -107,8 +127,7 @@ public ItemCommentsDto getItem(Long itemId, Long userId) { @Override public void deleteItem(Long id, Long ownerId) { Item item = itemRepository.findById(id) - .orElseThrow(() -> - new NotFoundException("Не найдена вещь id=" + id)); + .orElseThrow(() -> new NotFoundException("Не найдена вещь id=" + id)); if (!item.getOwner().getId().equals(ownerId)) { throw new AccessDeniedException("Удалять вещь может только хозяин."); } diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java similarity index 93% rename from src/main/java/ru/practicum/shareit/request/ItemRequest.java rename to server/src/main/java/ru/practicum/shareit/request/ItemRequest.java index 4b31d96..862af6c 100644 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -31,8 +31,8 @@ public class ItemRequest { private String description; @ManyToOne - @JoinColumn(name = "user_id") - private User requestor; + @JoinColumn(name = "customer_id") + private User customer; @Column(name = "created", nullable = false) private Instant created = Instant.now(); diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java new file mode 100644 index 0000000..7552266 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,60 @@ +package ru.practicum.shareit.request; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.RequestWithItemsDto; +import ru.practicum.shareit.request.service.ItemRequestService; +import ru.practicum.shareit.user.UserMapper; + +import java.util.List; + +/** + * Клас обработки запросов на Вещи + */ +@Slf4j +@RestController +@RequestMapping(path = "/requests") +public class ItemRequestController { + private ItemRequestService itemRequestService; + + public ItemRequestController(ItemRequestService itemRequestService) { + this.itemRequestService = itemRequestService; + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ItemRequestDto createItemRequest( + @RequestHeader(UserMapper.HEADER_USER_ID) final Long customerId, + @RequestBody final ItemRequestDto itemRequestDto) throws Exception { + log.info("Пользователь id={} делает заказ : {}", customerId, itemRequestDto.getDescription()); + return itemRequestService.create(customerId, itemRequestDto); + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public RequestWithItemsDto findItemRequest( + @RequestHeader(UserMapper.HEADER_USER_ID) final Long userId, + @PathVariable final Long id) { + log.info("Пользователь id={} просматривает заказ id={}", userId, id); + return itemRequestService.findReqestsById(userId, id); + } + + @GetMapping("") + @ResponseStatus(HttpStatus.OK) + public List findRequestsByUserId( + @RequestHeader(UserMapper.HEADER_USER_ID) final Long userId) { + log.info("Пользователь id={} просматривает свои заказы.", userId); + + return itemRequestService.findReqestsByCustomerId(userId); + } + + @GetMapping("/all") + @ResponseStatus(HttpStatus.OK) + public List findAllRequests( + @RequestHeader(UserMapper.HEADER_USER_ID) final Long userId) { + log.info("Пользователь id={} просматривает все заказы.", userId); + return itemRequestService.findAllReqests(userId); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java new file mode 100644 index 0000000..17f38a5 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.request; + +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.RequestWithItemsDto; +import ru.practicum.shareit.user.UserMapper; + +public class ItemRequestMapper { + private ItemRequestMapper() { + } + + public static ItemRequest toItemRequest(ItemRequestDto requestDto) { + ItemRequest itemRequest = new ItemRequest(); + itemRequest.setId(requestDto.getId()); + itemRequest.setDescription(requestDto.getDescription()); + if (requestDto.getRequestor() != null) { + itemRequest.setCustomer(UserMapper.toUser(requestDto.getRequestor())); + } + itemRequest.setCreated(requestDto.getCreated()); + return itemRequest; + } + + public static ItemRequestDto toItemRequestDto(ItemRequest itemRequest) { + ItemRequestDto itemRequestDto = new ItemRequestDto(); + itemRequestDto.setId(itemRequest.getId()); + itemRequestDto.setDescription(itemRequest.getDescription()); + if (itemRequest.getCustomer() != null) { + itemRequestDto.setRequestor(UserMapper.toUserDto(itemRequest.getCustomer())); + } + itemRequestDto.setCreated(itemRequest.getCreated()); + return itemRequestDto; + } + + public static RequestWithItemsDto toRwiDto(ItemRequest itemRequest) { + RequestWithItemsDto itemRequestDto = new RequestWithItemsDto(); + itemRequestDto.setId(itemRequest.getId()); + itemRequestDto.setDescription(itemRequest.getDescription()); + if (itemRequest.getCustomer() != null) { + itemRequestDto.setRequestor(UserMapper.toUserDto(itemRequest.getCustomer())); + } + itemRequestDto.setCreated(itemRequest.getCreated()); + return itemRequestDto; + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..ac43ec6 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.request.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.Instant; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ItemRequestDto { + private Long id; + private String description; + private UserDto requestor; + private Instant created; +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/RequestWithItemsDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/RequestWithItemsDto.java new file mode 100644 index 0000000..fc4c808 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/RequestWithItemsDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.item.dto.ItemShortDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class RequestWithItemsDto { + private Long id; + private String description; + private UserDto requestor; + private Instant created; + private List items = new ArrayList<>(); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java new file mode 100644 index 0000000..48fdd48 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.request.repository; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.shareit.request.ItemRequest; + +import java.util.List; + +public interface ItemRequestRepository extends JpaRepository { + List findAllByCustomer_IdEquals(Long customerId, Sort sort); + + @Query("SELECT ir FROM ItemRequest AS ir " + + "WHERE ir.customer.id <> ?1 " + + "ORDER BY ir.created DESC") + List findAllNotCustomer_Id(Long customerId); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java new file mode 100644 index 0000000..85570b4 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.request.service; + +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.RequestWithItemsDto; + +import java.util.List; + +public interface ItemRequestService { + ItemRequestDto create(Long customerId, ItemRequestDto itemRequestDto); + + RequestWithItemsDto findReqestsById(Long userId, Long id); + + List findReqestsByCustomerId(Long customerId); + + List findAllReqests(Long customerId); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java new file mode 100644 index 0000000..ac6f438 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -0,0 +1,110 @@ +package ru.practicum.shareit.request.service; + +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.dto.ItemShortDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.request.ItemRequestMapper; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.RequestWithItemsDto; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@Transactional(readOnly = true) +public class ItemRequestServiceImpl implements ItemRequestService { + private final ItemRequestRepository itemRequestRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + + public ItemRequestServiceImpl(ItemRequestRepository itemRequestRepository, + UserRepository userRepository, + ItemRepository itemRepository) { + this.itemRequestRepository = itemRequestRepository; + this.userRepository = userRepository; + this.itemRepository = itemRepository; + } + + @Override + @Transactional + public ItemRequestDto create(Long customerId, ItemRequestDto itemRequestDto) { + User customer = userRepository.findById(customerId) + .orElseThrow(() -> new NotFoundException("Не найден пользователь id=" + customerId)); + itemRequestDto.setRequestor(UserMapper.toUserDto(customer)); + itemRequestDto.setCreated(Instant.now()); + ItemRequest savedItemRequest = itemRequestRepository.save( + ItemRequestMapper.toItemRequest(itemRequestDto)); + return ItemRequestMapper.toItemRequestDto(savedItemRequest); + } + + @Override + public RequestWithItemsDto findReqestsById(Long userId, Long id) { + userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Не найден пользователь id=" + userId)); + ItemRequest request = itemRequestRepository.findById(id) + .orElseThrow(() -> new NotFoundException("Не найден запрос id=" + id)); + RequestWithItemsDto rwi = ItemRequestMapper.toRwiDto(request); + List items = itemRepository.findAllByRequest_IdEquals(id).stream() + .map(ItemMapper::toItemShortDto) + .toList(); + rwi.setItems(items); + return rwi; + } + + /** + * Поиск собственных запросов заказчика + * + * @param customerId - идентификатор заказчика + * @return - список запросов с ответными предложениями вещей + */ + @Override + public List findReqestsByCustomerId(Long customerId) { + userRepository.findById(customerId) + .orElseThrow(() -> new NotFoundException("Не найден пользователь id=" + customerId)); + List reqests = itemRequestRepository.findAllByCustomer_IdEquals( + customerId, Sort.by("created").descending()); + return addItemsToRequests(reqests); + } + + @Override + public List findAllReqests(Long customerId) { + userRepository.findById(customerId) + .orElseThrow(() -> new NotFoundException("Не найден пользователь id=" + customerId)); + List reqests = itemRequestRepository + .findAllNotCustomer_Id(customerId); + return addItemsToRequests(reqests); + } + + private List addItemsToRequests(List requests) { + List rwiDtos = new ArrayList<>(); + Map rwiDtoMap = new HashMap<>(); + for (int i = 0; i < requests.size(); i++) { + ItemRequest itemRequest = requests.get(i); + RequestWithItemsDto rwiDto = ItemRequestMapper.toRwiDto(itemRequest); + rwiDtos.add(rwiDto); + rwiDtoMap.put(itemRequest.getId(), rwiDto); + } + List ids = new ArrayList<>(rwiDtoMap.keySet()); + List items = itemRepository.findAllByRequest_IdIn(ids); + for (Item item : items) { + if (item.getRequest() != null) { + long requestId = item.getRequest().getId(); + rwiDtoMap.get(requestId).getItems().add(ItemMapper.toItemShortDto(item)); + } + } + return rwiDtos; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/server/src/main/java/ru/practicum/shareit/user/User.java similarity index 99% rename from src/main/java/ru/practicum/shareit/user/User.java rename to server/src/main/java/ru/practicum/shareit/user/User.java index c0022ff..24c278f 100644 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ b/server/src/main/java/ru/practicum/shareit/user/User.java @@ -20,11 +20,9 @@ public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @NotBlank(message = "Имя не может быть пустым", groups = {ValidAction.OnCreate.class}) @Column(name = "name", nullable = false) private String name; - @NotBlank(message = "Email не может быть пустым", groups = {ValidAction.OnCreate.class}) @Email(message = "Email должен удовлетворять правилам формирования почтовых адресов.", groups = {ValidAction.OnCreate.class, ValidAction.OnUpdate.class}) diff --git a/server/src/main/java/ru/practicum/shareit/user/UserController.java b/server/src/main/java/ru/practicum/shareit/user/UserController.java new file mode 100644 index 0000000..08f91d9 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,68 @@ +package ru.practicum.shareit.user; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import java.util.Collection; + +/** + * Класс обработки http запросов о пользователях. + */ +@Slf4j +@RestController +@RequestMapping(path = "/users") +public class UserController { + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public Collection findAllUser() { + log.info("Запрашиваем список всех пользователей."); + return userService.getAllUsers(); + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public UserDto findUser(@PathVariable Long id) { + log.info("Ищем пользователя id={}.", id); + return userService.getUserById(id); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public UserDto addNewUser(@RequestBody UserDto userDto) { + log.info("Создаем пользователя : {}.", userDto.toString()); + return userService.createUser(userDto); + } + + @PatchMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public UserDto updateUser(@PathVariable Long id, + @RequestBody UserDto updUser) { + updUser.setId(id); + log.info("Обновляем данные о пользователе : {}", updUser); + return userService.updateUser(updUser); + } + + @DeleteMapping + @ResponseStatus(HttpStatus.OK) + public Collection deleteAllUsers() { + log.info("Удаляем всех пользователей."); + return userService.deleteAllUsers(); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public void deleteUser(@PathVariable Long id) { + log.info("Удаляем пользователя id={}.", id); + userService.deleteUser(id); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/user/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java new file mode 100644 index 0000000..598576c --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user; + +import ru.practicum.shareit.user.dto.UserDto; + +public class UserMapper { + public static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + private 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()); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..6c16bf8 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.validator.ValidAction; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserDto { + private Long id; + private String name; + @Email(message = "Email должен удовлетворять правилам формирования почтовых адресов.", + groups = {ValidAction.OnCreate.class, ValidAction.OnUpdate.class}) + private String email; +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/repository/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/service/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserService.java diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java similarity index 79% rename from src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java index 0a3ccc1..55cdfa6 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -8,10 +8,11 @@ import ru.practicum.shareit.user.repository.UserRepository; import java.util.Collection; +import java.util.List; @Service public class UserServiceImpl implements UserService { - private final UserRepository userRepository; + private UserRepository userRepository; public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; @@ -26,8 +27,7 @@ public UserDto createUser(UserDto newUserDto) { @Override public UserDto getUserById(Long id) { User user = userRepository.findById(id) - .orElseThrow(() -> - new NotFoundException("Пользователь не найден id=" + id)); + .orElseThrow(() -> new NotFoundException("Пользователь не найден id=" + id)); return UserMapper.toUserDto(user); } @@ -35,8 +35,7 @@ public UserDto getUserById(Long id) { public UserDto updateUser(UserDto updUserDto) { Long id = updUserDto.getId(); User user = userRepository.findById(id) - .orElseThrow(() -> - new NotFoundException("Пользователь не найден id=" + id)); + .orElseThrow(() -> new NotFoundException("Пользователь не найден id=" + id)); if (updUserDto.getName() != null) { user.setName(updUserDto.getName()); } @@ -58,14 +57,12 @@ public void deleteUser(Long id) { @Override public Collection getAllUsers() { - return userRepository.findAll().stream() - .map(UserMapper::toUserDto) - .toList(); + return userRepository.findAll().stream().map(UserMapper::toUserDto).toList(); } @Override public Collection deleteAllUsers() { userRepository.deleteAll(); - return getAllUsers(); + return List.of(); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/validator/ValidAction.java b/server/src/main/java/ru/practicum/shareit/validator/ValidAction.java similarity index 100% rename from src/main/java/ru/practicum/shareit/validator/ValidAction.java rename to server/src/main/java/ru/practicum/shareit/validator/ValidAction.java diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties new file mode 100644 index 0000000..6a96840 --- /dev/null +++ b/server/src/main/resources/application.properties @@ -0,0 +1,17 @@ +server.port=9090 + +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.format_sql=true +spring.sql.init.mode=always + +#--- +spring.datasource.driverClassName=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:5432/shareit +spring.datasource.username=shareit +spring.datasource.password=shareit +#--- +spring.config.activate.on-profile=test +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:shareit +spring.datasource.username=shareit +spring.datasource.password=shareit \ No newline at end of file diff --git a/server/src/main/resources/schema.sql b/server/src/main/resources/schema.sql new file mode 100644 index 0000000..d886586 --- /dev/null +++ b/server/src/main/resources/schema.sql @@ -0,0 +1,64 @@ + +DROP TABLE IF EXISTS bookings; +DROP TABLE IF EXISTS comments; +DROP TABLE IF EXISTS items; +DROP TABLE IF EXISTS itemrequests; +DROP TABLE IF EXISTS users; + +CREATE TABLE IF NOT EXISTS users +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + CONSTRAINT pk_user PRIMARY KEY (id), + CONSTRAINT UQ_USER_EMAIL UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS itemrequests +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + description VARCHAR(512) NOT NULL, + customer_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_itemrequests PRIMARY KEY (id), + CONSTRAINT fk_itemrequests_to_users FOREIGN KEY (customer_id) REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS items +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + description VARCHAR(255), + owner_id BIGINT NOT NULL, + available BOOLEAN, + request_id BIGINT, + CONSTRAINT pk_item PRIMARY KEY (id), + CONSTRAINT fk_items_to_users FOREIGN KEY (owner_id) REFERENCES users (id), + CONSTRAINT fk_items_to_requests FOREIGN KEY (request_id) REFERENCES itemrequests (id) +); + +CREATE TABLE IF NOT EXISTS comments +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + text VARCHAR(512) NOT NULL, + item_id BIGINT NOT NULL, + author_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_comment PRIMARY KEY (id), + CONSTRAINT fk_connemts_to_items FOREIGN KEY (item_id) REFERENCES items (id), + CONSTRAINT fk_connemts_to_users FOREIGN KEY (author_id) REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS bookings +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + time_start TIMESTAMP WITHOUT TIME ZONE NOT NULL, + time_end TIMESTAMP WITHOUT TIME ZONE NOT NULL, + item_id BIGINT NOT NULL, + booker_id BIGINT NOT NULL, + status VARCHAR(16), + CONSTRAINT pk_buoking PRIMARY KEY (id), + CONSTRAINT fk_buokings_to_items FOREIGN KEY (item_id) REFERENCES items (id), + CONSTRAINT fk_buokings_to_users FOREIGN KEY (booker_id) REFERENCES users (id) +); + diff --git a/src/test/java/ru/practicum/shareit/ShareItAppTest.java b/server/src/test/java/ru/practicum/shareit/ShareItServerTest.java similarity index 73% rename from src/test/java/ru/practicum/shareit/ShareItAppTest.java rename to server/src/test/java/ru/practicum/shareit/ShareItServerTest.java index 91aa6f5..14ed97b 100644 --- a/src/test/java/ru/practicum/shareit/ShareItAppTest.java +++ b/server/src/test/java/ru/practicum/shareit/ShareItServerTest.java @@ -4,12 +4,16 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import static org.junit.jupiter.api.Assertions.*; - @SpringBootTest @AutoConfigureTestDatabase -class ShareItAppTest { +class ShareItServerTest { + @Test void contextLoads() throws Exception { } + + @Test + public void testMain() { + ShareItServer.main(new String[]{}); + } } \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java new file mode 100644 index 0000000..fcf3f45 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -0,0 +1,130 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.enums.BookingStatus; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = BookingController.class) +class BookingControllerTest { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + @Autowired + ObjectMapper mapper; + + @MockBean + private BookingService bookingService; + + private BookingDto testBookingDto = new BookingDto( + 1L, + LocalDateTime.of(2025, 04, 12, 10, 10), + LocalDateTime.of(2025, 04, 15, 10, 10), + 1L, + new ItemDto(1L, "Iteem", "Description", true, null, null, null), + 2L, + new UserDto(2L, "User", "user@booking.controller.test"), + BookingStatus.WAITING); + + @Autowired + private MockMvc mvc; + + @Test + void addBooking() throws Exception { + when(bookingService.addBooking(any(), anyLong())) + .thenReturn(testBookingDto); + + mvc.perform(post("/bookings") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(testBookingDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(testBookingDto.getId()), Long.class)) + .andExpect(jsonPath("$.bookerId", is(testBookingDto.getBookerId()), Long.class)); + } + + @Test + void editBooking() throws Exception { + when(bookingService.approvedBooking(anyLong(), anyLong(), anyBoolean())) + .thenReturn(testBookingDto); + + mvc.perform(patch("/bookings/1?approved=true") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(testBookingDto.getId()), Long.class)) + .andExpect(jsonPath("$.bookerId", is(testBookingDto.getBookerId()), Long.class)); + + } + + @Test + void findBooking() throws Exception { + when(bookingService.findBookingById(anyLong(), anyLong())) + .thenReturn(testBookingDto); + + mvc.perform(get("/bookings/1") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(testBookingDto.getId()), Long.class)) + .andExpect(jsonPath("$.bookerId", is(testBookingDto.getBookerId()), Long.class)); + + } + + @Test + void findBookingByBooker() throws Exception { + List bookingDtos = new ArrayList<>(); + bookingDtos.add(testBookingDto); + + when(bookingService.findBookingByBooker(anyLong(), any())) + .thenReturn(bookingDtos); + + mvc.perform(get("/bookings?state=ALL") + .header(HEADER_USER_ID, 2L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void findBookingByOwner() throws Exception { + List bookingDtos = new ArrayList<>(); + bookingDtos.add(testBookingDto); + + when(bookingService.findBookingsByOwner(anyLong(), any())) + .thenReturn(bookingDtos); + + mvc.perform(get("/bookings/owner?state=ALL") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/dto/BookingDtoTest.java b/server/src/test/java/ru/practicum/shareit/booking/dto/BookingDtoTest.java new file mode 100644 index 0000000..20384db --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/dto/BookingDtoTest.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import ru.practicum.shareit.booking.enums.BookingStatus; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class BookingDtoTest { + private final JacksonTester json; + + // BookingDto bookingDto = new BookingDto(); + @Test + void testBookingDto() throws Exception { + BookingDto bookingDto = new BookingDto(); + bookingDto.setId(1L); + bookingDto.setStart(LocalDateTime.now()); + bookingDto.setEnd(LocalDateTime.now().plusDays(1)); + bookingDto.setItemId(1L); + bookingDto.setBookerId(1L); + bookingDto.setStatus(BookingStatus.APPROVED); + + JsonContent result = json.write(bookingDto); + + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathNumberValue("$.itemId").isEqualTo(1); + assertThat(result).extractingJsonPathNumberValue("$.bookerId").isEqualTo(1); + } + + +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java new file mode 100644 index 0000000..905226b --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java @@ -0,0 +1,167 @@ +package ru.practicum.shareit.booking.service; + +import lombok.RequiredArgsConstructor; +import org.assertj.core.api.AssertionsForClassTypes; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.enums.BookingStatus; +import ru.practicum.shareit.booking.enums.SearchState; +import ru.practicum.shareit.excepton.InternalServerException; +import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.excepton.ValidationException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@Transactional +@SpringBootTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class BookingServiceImplTest { + + private final UserRepository userRepository; + private final ItemRepository itemRepository; + private final BookingServiceImpl bookingServiceImpl; + + Long ownerId = 0L; + Long itemId = 0L; + Long userId = 0L; + Long bookingId = 0L; + BookingDto sourceBookingDto; + + @Test + void addBooking() throws Exception { + User user = new User(); + user.setName("User1"); + user.setEmail("user1@booking.test"); + User owner = userRepository.save(user); + assertThat(owner.getId(), notNullValue()); + ownerId = owner.getId(); + + user = new User(); + user.setName("User2"); + user.setEmail("user2@booking.test"); + User savedUser = userRepository.save(user); + assertThat(savedUser.getId(), notNullValue()); + userId = savedUser.getId(); + + Item item = new Item(); + item.setName("Item1"); + item.setDescription("Item1"); + item.setOwner(owner); + item.setAvailable(true); + item.setRequest(null); + Item savedItem = itemRepository.save(item); + assertThat(savedItem.getId(), notNullValue()); + itemId = savedItem.getId(); + + BookingDto bookingDto = new BookingDto(); + bookingDto.setItemId(itemId); + bookingDto.setBookerId(userId); + bookingDto.setStart(LocalDateTime.of(2025, 04, 12, 10, 10)); + bookingDto.setEnd(LocalDateTime.of(2025, 04, 15, 10, 10)); + bookingDto.setStatus(BookingStatus.WAITING); + + sourceBookingDto = bookingServiceImpl.addBooking(bookingDto, userId); + assertThat(sourceBookingDto.getId(), notNullValue()); + bookingId = sourceBookingDto.getId(); + + assertThrows(InternalServerException.class, + () -> { + bookingDto.setItemId(null); + bookingServiceImpl.addBooking(bookingDto, userId); + }, + "Некорректные данные должны приводить к исключению."); + } + + @Test + void findBookingById() throws Exception { + if (bookingId.equals(0L)) { + addBooking(); + } + + BookingDto bookingDto = bookingServiceImpl.findBookingById( + bookingId, userId); + assertThat(bookingDto, notNullValue()); + AssertionsForClassTypes.assertThat(bookingDto) + .usingRecursiveComparison() + .isEqualTo(sourceBookingDto); + + assertThrows(NotFoundException.class, + () -> { + bookingServiceImpl.findBookingById( + 1000L, userId); + }, + "Чтение Несуществующего бронирования должно приводить к исключению."); + } + + @Test + void approvedBooking() throws Exception { + if (bookingId.equals(0L)) { + addBooking(); + } + + BookingDto bookingDto = bookingServiceImpl.approvedBooking(bookingId, ownerId, true); + assertNotNull(bookingDto); + assertEquals(bookingId, bookingDto.getId()); + + assertThrows(ValidationException.class, + () -> { + bookingServiceImpl.approvedBooking(bookingId, userId, true); + }, + "Изменение статуса бронирования не хозяином должно приводить к исключению."); + } + + @Test + void findBookingsByOwner() throws Exception { + if (bookingId.equals(0L)) { + addBooking(); + } + List bookingDtoList = bookingServiceImpl.findBookingsByOwner( + ownerId, SearchState.PAST); + assertThat(bookingDtoList, notNullValue()); + assertTrue(bookingDtoList.size() > 0); + + bookingDtoList = bookingServiceImpl.findBookingsByOwner( + ownerId, SearchState.CURRENT); + assertThat(bookingDtoList, notNullValue()); + assertTrue(bookingDtoList.isEmpty()); + + bookingDtoList = bookingServiceImpl.findBookingsByOwner( + ownerId, SearchState.FUTURE); + assertThat(bookingDtoList, notNullValue()); + assertTrue(bookingDtoList.isEmpty()); + } + + @Test + void findBookingByBooker() throws Exception { + if (bookingId.equals(0L)) { + addBooking(); + } + List bookingDtoList = bookingServiceImpl.findBookingByBooker( + userId, SearchState.PAST); + assertThat(bookingDtoList, notNullValue()); + assertTrue(bookingDtoList.size() > 0); + + bookingDtoList = bookingServiceImpl.findBookingByBooker( + userId, SearchState.CURRENT); + assertThat(bookingDtoList, notNullValue()); + assertTrue(bookingDtoList.isEmpty()); + + bookingDtoList = bookingServiceImpl.findBookingByBooker( + userId, SearchState.FUTURE); + assertThat(bookingDtoList, notNullValue()); + assertTrue(bookingDtoList.isEmpty()); + + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java new file mode 100644 index 0000000..a46aff1 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -0,0 +1,203 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemCommentsDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.service.CommentService; +import ru.practicum.shareit.item.service.ItemService; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = ItemController.class) +class ItemControllerTest { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + @Autowired + ObjectMapper mapper; + @MockBean + private ItemService itemService; + @MockBean + private CommentService commentService; + + @Autowired + private MockMvc mvc; + + @Test + void findAllItems() throws Exception { + List sourceItems = makeItems(3); + + when(itemService.getItemsByOwnerId(anyLong())) + .thenReturn(sourceItems); + + mvc.perform(get("/items") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(sourceItems.size())); + } + + @Test + public void onSearch() throws Exception { + List sourceItems = makeItems(2); + + when(itemService.searchItemsByText(anyString())) + .thenReturn(sourceItems); + + mvc.perform(get("/items/search") + .header(HEADER_USER_ID, 1L) + .param("text", "test") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(sourceItems.size())); + + } + + @Test + void findItemById() throws Exception { + ItemCommentsDto itemDto = new ItemCommentsDto(); + itemDto.setId(1L); + itemDto.setName("Item2"); + itemDto.setDescription("controller test find item by id"); + itemDto.setAvailable(true); + + when(itemService.getItem(anyLong(), anyLong())) + .thenReturn(itemDto); + + mvc.perform(get("/items/1") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(itemDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(itemDto.getName()))) + .andExpect(jsonPath("$.description", is(itemDto.getDescription()))); + } + + @Test + void createItem() throws Exception { + ItemDto itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("Name item3"); + itemDto.setDescription("controller test create new item"); + itemDto.setAvailable(true); + + when(itemService.addItem(any(), anyLong())) + .thenReturn(itemDto); + + mvc.perform(post("/items") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(itemDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(itemDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(itemDto.getName()))) + .andExpect(jsonPath("$.description", is(itemDto.getDescription()))); + } + + @Test + void updateItem() throws Exception { + ItemDto itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("Name item4"); + itemDto.setDescription("controller test update item"); + itemDto.setAvailable(true); + + when(itemService.updateItem(any(), anyLong())) + .thenReturn(itemDto); + mvc.perform(patch("/items/1") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(itemDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(itemDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(itemDto.getName()))) + .andExpect(jsonPath("$.description", is(itemDto.getDescription()))); + } + + @Test + void deleteItem() throws Exception { + mvc.perform(delete("/items/1") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + Mockito.verify(itemService, Mockito.times(1)) + .deleteItem(1L, 1L); + } + + @Test + void deleteAllItems() throws Exception { + mvc.perform(delete("/items") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + Mockito.verify(itemService, Mockito.times(1)) + .deleteAllItems(); + } + + @Test + void addComment() throws Exception { + CommentDto commentDto = new CommentDto(); + commentDto.setId(1L); + commentDto.setItemId(1L); + commentDto.setText("comment item"); + commentDto.setAuthorId(2L); + commentDto.setCreated(Instant.now()); + + when(commentService.addComment(any())) + .thenReturn(commentDto); + + mvc.perform(post("/items/1/comment") + .header(HEADER_USER_ID, 2L) + .content(mapper.writeValueAsString(commentDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + } + + private List makeItems(Integer count) { + List sourceItems = new ArrayList<>(); + int maxItems = 3; + for (int i = 1; i <= count; i++) { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Item_" + i); + itemDto.setDescription("controller test item_" + i); + itemDto.setAvailable(true); + sourceItems.add(itemDto); + } + return sourceItems; + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/dto/CommentDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/dto/CommentDtoTest.java new file mode 100644 index 0000000..74b2297 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dto/CommentDtoTest.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.item.dto; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; + +import java.time.Instant; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class CommentDtoTest { + private final JacksonTester json; + + @Test + void serialize() throws Exception { + CommentDto commentDto = new CommentDto( + 1L, + "Text", + 1L, + "user1", + 1L, + Instant.now() + ); + + JsonContent result = json.write(commentDto); + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.text").isEqualTo("Text"); + assertThat(result).extractingJsonPathNumberValue("$.authorId").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.authorName").isEqualTo("user1"); + assertThat(result).extractingJsonPathNumberValue("$.itemId").isEqualTo(1); + + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/dto/ItemDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/dto/ItemDtoTest.java new file mode 100644 index 0000000..dac6e9e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dto/ItemDtoTest.java @@ -0,0 +1,36 @@ +package ru.practicum.shareit.item.dto; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class ItemDtoTest { + private final JacksonTester json; + + @Test + void testItemDto() throws Exception { + ItemDto itemDto = new ItemDto( + 1L, + "Item", + "Test item", + true, + null, + null, + null); + + JsonContent result = json.write(itemDto); + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("Item"); + assertThat(result).extractingJsonPathStringValue("$.description").isEqualTo("Test item"); + assertThat(result).extractingJsonPathBooleanValue("$.available").isEqualTo(true); + assertThat(result).extractingJsonPathBooleanValue("$.requestId").isEqualTo(null); + } + +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/dto/ItemShortDtoTest.java b/server/src/test/java/ru/practicum/shareit/item/dto/ItemShortDtoTest.java new file mode 100644 index 0000000..3261101 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dto/ItemShortDtoTest.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.item.dto; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class ItemShortDtoTest { + private final JacksonTester json; + + @Test + void testSerialize() throws Exception { + ItemShortDto itemShortDto = new ItemShortDto( + 1L, + "Item", + 1L); + JsonContent result = json.write(itemShortDto); + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("Item"); + assertThat(result).extractingJsonPathNumberValue("$.ownerId").isEqualTo(1); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/model/ItemTest.java b/server/src/test/java/ru/practicum/shareit/item/model/ItemTest.java new file mode 100644 index 0000000..74f4034 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/model/ItemTest.java @@ -0,0 +1,34 @@ +package ru.practicum.shareit.item.model; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class ItemTest { + private final JacksonTester json; + + @Test + void serialize() throws Exception { + Item item = new Item( + 1L, + "Item", + "Item description", + true, + null + ); + JsonContent result = json.write(item); + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("Item"); + assertThat(result).extractingJsonPathStringValue("$.description").isEqualTo("Item description"); + assertThat(result).extractingJsonPathBooleanValue("$.available").isEqualTo(true); + + } + +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/service/CommentServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/item/service/CommentServiceImplTest.java new file mode 100644 index 0000000..271ad1e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/service/CommentServiceImplTest.java @@ -0,0 +1,88 @@ +package ru.practicum.shareit.item.service; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.booking.Booking; +import ru.practicum.shareit.booking.enums.BookingStatus; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Transactional +@SpringBootTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class CommentServiceImplTest { + + private final UserRepository userRepository; + private final ItemRepository itemRepository; + private final BookingRepository bookingRepository; + private final CommentService commentService; + + @Test + void addComment() { + User user = new User(); + user.setName("User1"); + user.setEmail("user1@comment.test"); + User owner = userRepository.save(user); + assertThat(owner.getId(), notNullValue()); + + Item item = new Item(); + item.setName("Item1"); + item.setDescription("Item1"); + item.setOwner(owner); + item.setAvailable(true); + item.setRequest(null); + Item savedItem = itemRepository.save(item); + assertThat(savedItem.getId(), notNullValue()); + + user = new User(); + user.setName("User2"); + user.setEmail("user2@comment.test"); + User booker = userRepository.save(user); + assertThat(booker.getId(), notNullValue()); + + Booking booking = new Booking(); + booking.setItem(item); + booking.setBooker(booker); + booking.setStart((LocalDateTime.of(2025, 04, 12, 10, 10)).toInstant(ZoneOffset.UTC)); + booking.setEnd((LocalDateTime.of(2025, 04, 15, 10, 10)).toInstant(ZoneOffset.UTC)); + booking.setStatus(BookingStatus.APPROVED); + + Booking savedBooking = bookingRepository.save(booking); + assertThat(savedBooking.getId(), notNullValue()); + + CommentDto commentDto = new CommentDto(); + commentDto.setItemId(item.getId()); + commentDto.setText("Text."); + commentDto.setAuthorId(booker.getId()); + commentDto.setCreated(Instant.now()); + + CommentDto commentDto1 = commentService.addComment(commentDto); + assertThat(commentDto1.getId(), notNullValue()); + assertEquals(commentDto1.getText(), commentDto.getText()); + assertEquals(commentDto1.getItemId(), commentDto1.getItemId()); + + commentDto.setAuthorId(savedItem.getId()); + assertThrows(NotFoundException.class, + () -> { + commentService.addComment(commentDto); + }, + "Коментирование хозяином должно приводить к исключению."); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java new file mode 100644 index 0000000..e2e7fa6 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/service/ItemServiceImplTest.java @@ -0,0 +1,164 @@ +package ru.practicum.shareit.item.service; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.excepton.AccessDeniedException; +import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.item.dto.ItemCommentsDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +@Transactional +@SpringBootTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class ItemServiceImplTest { + private final ItemService itemService; + private final UserService userService; + + Long ownerId = 0L; + Long itemId = 0L; + ItemDto testItemDto; + Long testUserId = 0L; + + @BeforeEach + void setUp() { + if (ownerId == 0L) { + // создаем пользователя - хозяина вещей + UserDto userDto = new UserDto(); + userDto.setName("User1"); + userDto.setEmail("user1@items.test"); + UserDto savedUserDto = userService.createUser(userDto); + assertThat(savedUserDto.getId(), notNullValue()); + ownerId = savedUserDto.getId(); + + userDto = new UserDto(); + userDto.setName("User2"); + userDto.setEmail("user2@items.test"); + savedUserDto = userService.createUser(userDto); + testUserId = savedUserDto.getId(); + } + itemService.deleteAllItems(); + } + + @Test + void addItem() throws Exception { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Item1"); + itemDto.setDescription("Item1"); + itemDto.setAvailable(true); + itemDto.setRequestId(null); + ItemDto savedItem = itemService.addItem(itemDto, ownerId); + assertThat(savedItem.getId(), notNullValue()); + assertEquals(savedItem.getName(), itemDto.getName()); + assertEquals(savedItem.getDescription(), itemDto.getDescription()); + itemId = savedItem.getId(); + testItemDto = savedItem; + } + + @Test + void updateItem() throws Exception { + if (itemId == 0L) { + addItem(); + } + ItemDto updItemDto = new ItemDto(); + updItemDto.setId(itemId); + updItemDto.setDescription("Item1 uddated"); + + ItemDto itemDto = itemService.updateItem(updItemDto, ownerId); + assertThat(itemDto.getId(), notNullValue()); + assertEquals(itemDto.getDescription(), updItemDto.getDescription()); + + assertThrows(AccessDeniedException.class, + () -> { + itemService.updateItem(updItemDto, testUserId); + }, + "редактирование не хозяином вещи должно приводить к исключению."); + } + + @Test + void getItems() throws Exception { + if (itemId == 0L) { + addItem(); + } + ItemCommentsDto icd = itemService.getItem(itemId, ownerId); + assertThat(icd.getId(), notNullValue()); + assertEquals(testItemDto.getId(), icd.getId()); + assertEquals(testItemDto.getName(), icd.getName()); + assertEquals(testItemDto.getDescription(), icd.getDescription()); + } + + @Test + void deleteItem() throws Exception { + if (itemId == 0L) { + addItem(); + } + itemService.deleteItem(itemId, ownerId); + assertThrows(NotFoundException.class, + () -> { + itemService.getItem(itemId, ownerId); + }, + "Чтение удаленной вещи должно приводить к исключению."); + } + + /** + * Тестируем чтение списка вещей пользователя + */ + @Test + void getItemsByOwnerId() { + // заполняем список вещей + List sourceItems = makeItems(ownerId, 3); + + // Чидаем список вещей + List targetItems = itemService.getItemsByOwnerId(ownerId) + .stream().toList(); + + assertThat(targetItems, notNullValue()); + assertThat(targetItems, hasSize(sourceItems.size())); + for (ItemDto sourceItem : sourceItems) { + assertThat(targetItems, hasItem(allOf( + hasProperty("id", notNullValue()), + hasProperty("name", equalTo(sourceItem.getName())), + hasProperty("description", equalTo(sourceItem.getDescription())) + ))); + } + } + + @Test + void searchItems() throws Exception { + // заполняем список вещей + List sourceItems = makeItems(ownerId, 3); + + List targetItems = itemService.searchItemsByText("Item_2") + .stream().toList(); + assertNotNull(targetItems); + assertTrue(targetItems.size() > 0); + } + + private List makeItems(Long ownerId, Integer count) { + List sourceItems = new ArrayList<>(); + int maxItems = 3; + for (int i = 1; i <= count; i++) { + ItemDto itemDto = new ItemDto(); + itemDto.setName("Item_" + i); + itemDto.setDescription("Itemms test item_" + i); + itemDto.setAvailable(true); + sourceItems.add(itemDto); + itemService.addItem(itemDto, ownerId); + } + return sourceItems; + } + +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java new file mode 100644 index 0000000..28ad613 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java @@ -0,0 +1,126 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.RequestWithItemsDto; +import ru.practicum.shareit.request.service.ItemRequestService; +import ru.practicum.shareit.user.dto.UserDto; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = ItemRequestController.class) +class ItemRequestControllerTest { + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + @Autowired + ObjectMapper mapper; + @MockBean + private ItemRequestService itemRequestService; + @Autowired + private MockMvc mvc; + + @Test + void createItemRequest() throws Exception { + ItemRequestDto itemRequestDto = new ItemRequestDto(); + itemRequestDto.setId(1L); + itemRequestDto.setDescription("description"); + itemRequestDto.setRequestor(new UserDto(1L, "user", "user@request.crate.test")); + itemRequestDto.setCreated(Instant.now()); + + when(itemRequestService.create(anyLong(), any())) + .thenReturn(itemRequestDto); + + mvc.perform(post("/requests") + .header(HEADER_USER_ID, 1L) + .content(mapper.writeValueAsString(itemRequestDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(itemRequestDto.getId()), Long.class)) + .andExpect(jsonPath("$.description", is(itemRequestDto.getDescription()))); + + } + + @Test + void findItemRequest() throws Exception { + RequestWithItemsDto rwi = new RequestWithItemsDto(); + rwi.setId(1L); + rwi.setDescription("description"); + rwi.setRequestor(new UserDto(1L, "user", "user@request.crate.test")); + rwi.setCreated(Instant.now()); + when(itemRequestService.findReqestsById(anyLong(), anyLong())) + .thenReturn(rwi); + + mvc.perform(get("/requests/1") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(rwi.getId()), Long.class)) + .andExpect(jsonPath("$.description", is(rwi.getDescription()))); + + } + + @Test + void findRequestsByUserId() throws Exception { + List rwiList = makeRequests(2); + + when(itemRequestService.findReqestsByCustomerId(anyLong())) + .thenReturn(rwiList); + + mvc.perform(get("/requests") + .header(HEADER_USER_ID, 1L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + void findAllRequests() throws Exception { + List rwiList = makeRequests(3); + + when(itemRequestService.findAllReqests(anyLong())) + .thenReturn(rwiList); + + mvc.perform(get("/requests/all") + .header(HEADER_USER_ID, 2L) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + private List makeRequests(Integer count) { + List rwiList = new ArrayList<>(); + int maxItems = 3; + for (int i = 1; i <= count; i++) { + RequestWithItemsDto rwi = new RequestWithItemsDto(); + rwi.setId((long) i); + rwi.setDescription("controller test item_" + i); + rwi.setRequestor(new UserDto(1L, "user", "user@request.crate.test")); + rwi.setCreated(Instant.now().plusSeconds(60 * i)); + rwiList.add(rwi); + } + return rwiList; + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceImplTest.java new file mode 100644 index 0000000..d2bf031 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceImplTest.java @@ -0,0 +1,115 @@ +package ru.practicum.shareit.request.service; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.ItemRequestMapper; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.RequestWithItemsDto; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.Instant; +import java.util.List; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@Transactional +@SpringBootTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class ItemRequestServiceImplTest { + private final ItemRequestService itemRequestService; + private final UserRepository userRepository; + + Long userId = 0L; + Long requestId = 0L; + @Autowired + private ItemRepository itemRepository; + + @Test + void create() { + User newUser = new User(1L, "UserName", "user@request.test"); + User savedUser = userRepository.save(newUser); + assertNotNull(savedUser, + "Пользователь не соххраняется в репозитории."); + userId = savedUser.getId(); + + ItemRequestDto itemRequestDto = new ItemRequestDto(); + itemRequestDto.setDescription("request test description"); + itemRequestDto.setCreated(Instant.now()); + ItemRequestDto savedRequestDto = itemRequestService.create( + userId, + itemRequestDto); + assertNotNull(savedRequestDto, + "Запрос не соххраняется в репозитории."); + requestId = savedRequestDto.getId(); + } + + + @Test + void findReqestsById() throws NotFoundException, Exception { + if (requestId.equals(0L)) { + create(); + } + RequestWithItemsDto rwi = + itemRequestService.findReqestsById(userId, requestId); + assertNotNull(rwi, "Запрос не найден."); + assertEquals(requestId, rwi.getId(), + "Идентификатор запроса не верен."); + + assertThrows(NotFoundException.class, + () -> { + itemRequestService.findReqestsById(userId, 1000L); + }, + "Чтение Несуществующего запроса должно приводить к исключению."); + } + + @Test + void findReqestsByCustomerId() { + if (userId.equals(0L)) { + create(); + } + ItemRequestDto itemRequestDto = new ItemRequestDto(); + itemRequestDto.setDescription("request test by customer id"); + itemRequestDto.setCreated(Instant.now()); + ItemRequestDto savedRequestDto = itemRequestService.create(userId, itemRequestDto); + List rwiList = itemRequestService.findReqestsByCustomerId(userId); + assertThat(rwiList, notNullValue()); + assertTrue(rwiList.size() > 1); + } + + @Test + void findAllReqests() throws NotFoundException, Exception { + if (userId.equals(0L)) { + create(); + } + ItemRequestDto itemRequestDto = new ItemRequestDto(); + itemRequestDto.setDescription("request test find all"); + itemRequestDto.setCreated(Instant.now()); + ItemRequestDto savedRequestDto = itemRequestService.create(userId, itemRequestDto); + + Item item = new Item(1L, "Item", "Description", + userRepository.findById(userId).get(), + true, + ItemRequestMapper.toItemRequest(savedRequestDto)); + item = itemRepository.save(item); + + // для поиска "чужих" запросов заведем еще одного пользователя + User newUser = new User(2L, "UserName2", "user2@request.test"); + User savedUser = userRepository.save(newUser); + + // ищем все "чужие запросы" + List rwiList = + itemRequestService.findAllReqests(savedUser.getId()); + assertThat(rwiList, notNullValue()); + assertTrue(rwiList.size() > 1); + } +} + diff --git a/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java new file mode 100644 index 0000000..4012507 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,133 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = UserController.class) +class UserControllerTest { + @MockBean + private final UserService userService; + @Autowired + ObjectMapper mapper; + @Autowired + private MockMvc mvc; + private UserDto userDto = new UserDto( + 1L, + "User Test", + "user@controller.test"); + + @Autowired + public UserControllerTest(UserService userService) { + this.userService = userService; + } + + @Test + void findAllUser() throws Exception { + List sourceUsers = List.of( + new UserDto(1L, "User1", "user1@controller.test"), + new UserDto(2L, "User2", "user2@controller.test"), + new UserDto(3L, "User3", "user3@controller.test")); + when(userService.getAllUsers()) + .thenReturn(sourceUsers); + + mvc.perform(get("/users") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(3)); + } + + @Test + void findUser() throws Exception { + when(userService.getUserById(anyLong())) + .thenReturn(userDto); + + mvc.perform(get("/users/1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(userDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(userDto.getName()))) + .andExpect(jsonPath("$.email", is(userDto.getEmail()))); + } + + @Test + void addNewUser() throws Exception { + when(userService.createUser(any())) + .thenReturn(userDto); + + mvc.perform(post("/users") + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id", is(userDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(userDto.getName()))) + .andExpect(jsonPath("$.email", is(userDto.getEmail()))); + } + + @Test + void updateUser() throws Exception { + when(userService.updateUser(any())) + .thenReturn(userDto); + + mvc.perform(patch("/users/1") + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(userDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(userDto.getName()))) + .andExpect(jsonPath("$.email", is(userDto.getEmail()))); + } + + @Test + void deleteAllUsers() throws Exception { + when(userService.deleteAllUsers()) + .thenReturn(List.of()); + + mvc.perform(delete("/users") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + Mockito.verify(userService, Mockito.times(1)) + .deleteAllUsers(); + } + + @Test + void deleteUser() throws Exception { + mvc.perform(delete("/users/1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + Mockito.verify(userService, Mockito.times(1)) + .deleteUser(anyLong()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/dto/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/user/dto/UserDtoTest.java new file mode 100644 index 0000000..6a86e82 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/dto/UserDtoTest.java @@ -0,0 +1,30 @@ +package ru.practicum.shareit.user.dto; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class UserDtoTest { + private final JacksonTester json; + + @Test + void testUserDto() throws Exception { + UserDto userDto = new UserDto( + 1L, + "User", + "user-dto@json.test"); + + JsonContent result = json.write(userDto); + + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("User"); + assertThat(result).extractingJsonPathStringValue("$.email").isEqualTo("user-dto@json.test"); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java new file mode 100644 index 0000000..8b30d40 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/service/UserServiceImplTest.java @@ -0,0 +1,141 @@ +package ru.practicum.shareit.user.service; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.excepton.NotFoundException; +import ru.practicum.shareit.user.dto.UserDto; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Тестирование сервиса работы с пользователями + */ +@Transactional +@SpringBootTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class UserServiceImplTest { + private final UserService userService; + + @Test + void createUser() { + UserDto userDto = new UserDto(1L, + "User Test create", + "user@test.create"); + UserDto savedUserDto = userService.createUser(userDto); + assertNotNull(savedUserDto, + "Пользователь не соххраняется в репозитории :" + userDto.toString()); + + assertThat(savedUserDto.getId(), notNullValue()); + assertThat(savedUserDto.getEmail(), equalTo(userDto.getEmail())); + assertThat(savedUserDto.getName(), equalTo(userDto.getName())); + } + + @Test + void getUserById() { + UserDto userDto = new UserDto(1L, + "User Test getById", + "user@get.by.id.test"); + UserDto savedUserDto = userService.createUser(userDto); + assertNotNull(savedUserDto, + "Пользователь не соххраняется в репозитории :" + userDto); + + Long userId = savedUserDto.getId(); + UserDto userDto2 = userService.getUserById(userId); + + assertThat(userDto2) + .usingRecursiveComparison() + .isEqualTo(savedUserDto); + + assertThrows(NotFoundException.class, + () -> { + userService.getUserById(100L); + }, + "Чтение несуществующего пользователя должно приводить к исключению."); + } + + @Test + void updateUser() { + UserDto userDto = new UserDto(1L, + "User Test update", + "user@update.test"); + UserDto savedUserDto = userService.createUser(userDto); + assertNotNull(savedUserDto, + "Пользователь не соххраняется в репозитории :" + userDto); + + savedUserDto.setEmail("update@test.update"); + UserDto updatedUserDto = userService.updateUser(savedUserDto); + + assertThat(updatedUserDto, notNullValue()); + assertThat(updatedUserDto) + .usingRecursiveComparison() + .isEqualTo(savedUserDto); + + savedUserDto.setId(9999L); + assertThrows(NotFoundException.class, + () -> { + userService.updateUser(savedUserDto); + }, + "Редактирование несуществующего пользователя должно приводить к исключению."); + } + + @Test + void deleteUser() { + UserDto userDto = new UserDto(1L, + "User Test update", + "user@update.test"); + UserDto savedUserDto = userService.createUser(userDto); + assertNotNull(savedUserDto, + "Пользователь не соххраняется в репозитории :" + userDto); + + Long userId = savedUserDto.getId(); + userService.deleteUser(userId); + + assertThrows(NotFoundException.class, + () -> { + userService.deleteUser(1000L); + }, + "Удаление несуществующего пользователя должно приводить к исключению."); + } + + @Test + void getAllUsers() { + List sourceUsers = List.of( + new UserDto(1L, "User1", "user1@get.all.test"), + new UserDto(2L, "User2", "user2@get.all.test"), + new UserDto(3L, "User3", "user3@get.all.test") + ); + + for (UserDto user : sourceUsers) { + userService.createUser(user); + } + + List targetUsers = userService.getAllUsers().stream().toList(); + + assertThat(targetUsers, notNullValue()); + assertThat(targetUsers, hasSize(sourceUsers.size())); + for (UserDto sourceUser : sourceUsers) { + assertThat(targetUsers, hasItem(allOf( + hasProperty("id", notNullValue()), + hasProperty("name", equalTo(sourceUser.getName())), + hasProperty("email", equalTo(sourceUser.getEmail())) + ))); + } + } + + @Test + void deleteAllUsers() { + List targetUsers = userService.deleteAllUsers().stream().toList(); + assertThat(targetUsers, notNullValue()); + assertThat(targetUsers, hasSize(0)); + } +} \ No newline at end of file diff --git a/server/src/test/resoueces/application.properties b/server/src/test/resoueces/application.properties new file mode 100644 index 0000000..285ee96 --- /dev/null +++ b/server/src/test/resoueces/application.properties @@ -0,0 +1,17 @@ +server.port=9090 + +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.format_sql=true +spring.sql.init.mode=always + +#--- +#spring.datasource.driverClassName=org.postgresql.Driver +#spring.datasource.url=jdbc:postgresql://localhost:5432/shareit +#spring.datasource.username=shareit +#spring.datasource.password=shareit +#--- +#spring.config.activate.on-profile=test +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:shareit +spring.datasource.username=shareit +spring.datasource.password=shareit diff --git a/server/src/test/resoueces/schema.sql b/server/src/test/resoueces/schema.sql new file mode 100644 index 0000000..9ca3517 --- /dev/null +++ b/server/src/test/resoueces/schema.sql @@ -0,0 +1,63 @@ +DROP TABLE IF EXISTS bookings; +DROP TABLE IF EXISTS comments; +DROP TABLE IF EXISTS items; +DROP TABLE IF EXISTS itemrequests; +DROP TABLE IF EXISTS users; + +CREATE TABLE IF NOT EXISTS users +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + CONSTRAINT pk_user PRIMARY KEY (id), + CONSTRAINT UQ_USER_EMAIL UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS itemrequests +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + description VARCHAR(512) NOT NULL, + customer_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_itemrequests PRIMARY KEY (id), + CONSTRAINT fk_itemrequests_to_users FOREIGN KEY (customer_id) REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS items +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name VARCHAR(255) NOT NULL, + description VARCHAR(255), + owner_id BIGINT NOT NULL, + available BOOLEAN, + request_id BIGINT, + CONSTRAINT pk_item PRIMARY KEY (id), + CONSTRAINT fk_items_to_users FOREIGN KEY (owner_id) REFERENCES users (id), + CONSTRAINT fk_items_to_requests FOREIGN KEY (request_id) REFERENCES itemrequests (id) +); + +CREATE TABLE IF NOT EXISTS comments +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + text VARCHAR(512) NOT NULL, + item_id BIGINT NOT NULL, + author_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_comment PRIMARY KEY (id), + CONSTRAINT fk_connemts_to_items FOREIGN KEY (item_id) REFERENCES items (id), + CONSTRAINT fk_connemts_to_users FOREIGN KEY (author_id) REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS bookings +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + time_start TIMESTAMP WITHOUT TIME ZONE NOT NULL, + time_end TIMESTAMP WITHOUT TIME ZONE NOT NULL, + item_id BIGINT NOT NULL, + booker_id BIGINT NOT NULL, + status VARCHAR(16), + CONSTRAINT pk_buoking PRIMARY KEY (id), + CONSTRAINT fk_buokings_to_items FOREIGN KEY (item_id) REFERENCES items (id), + CONSTRAINT fk_buokings_to_users FOREIGN KEY (booker_id) REFERENCES users (id) +); + diff --git a/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java b/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java deleted file mode 100644 index 5a04d0d..0000000 --- a/src/main/java/ru/practicum/shareit/advisor/ErrorAdvisor.java +++ /dev/null @@ -1,108 +0,0 @@ -package ru.practicum.shareit.advisor; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageNotReadableException; -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 ru.practicum.shareit.excepton.*; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Класс обработки исключений при обработке поступивших http запросов - */ -@Slf4j -@RestControllerAdvice -public class ErrorAdvisor { - - /** - * Обработка исключения MethodArgumentNotValidException - при проверке аргумента метода - * - * @param e - исключение - * @return - список нарушений для отображения в теле ответа - */ - @ExceptionHandler(MethodArgumentNotValidException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public List onMethodArgumentNotValidException( - MethodArgumentNotValidException e - ) { - final List violations = e.getBindingResult().getFieldErrors().stream() - .map(error -> new ErrorMessage("[" + error.getField() + "] " - + error.getDefaultMessage())) - .collect(Collectors.toList()); - log.info("400 {}.", e.getMessage()); - return violations; - } - - /** - * Метод обработки пользовательского исключения ValidationException - * - * @param exception - исключение проверки данных - * @return - объект для http ответа с сообщением об ошибке - */ - @ExceptionHandler(ValidationException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorMessage onValidationException(ValidationException exception) { - log.info("400 {}.", exception.getMessage()); - return new ErrorMessage(exception.getMessage()); - } - - @ExceptionHandler(NotFoundException.class) - @ResponseStatus(HttpStatus.NOT_FOUND) - public ErrorMessage notFoundObject(NotFoundException exception) { - log.info("404 {}.", exception.getMessage()); - return new ErrorMessage(exception.getMessage()); - } - - /** - * Обработка исключения HttpMessageNotReadableException при поступлении пустого запроса - * - * @param e - исключение генерируемое при отсутствии обязательных данных в теле запроса - * @return - объект для http ответа с сообщением об ошибке - */ - @ExceptionHandler(HttpMessageNotReadableException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorMessage onHttpMessageNotReadableException( - HttpMessageNotReadableException e) { - log.info("400 {}.", e.getMessage()); - return new ErrorMessage("В запросе отсутствуют необходимые данные." + e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorMessage onInternalException(final InternalServerException e) { - log.warn("500 {}", e.getMessage()); - return new ErrorMessage(e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.FORBIDDEN) - public ErrorMessage onAccessDeniedException(final AccessDeniedException e) { - log.warn("403 {}", e.getMessage()); - return new ErrorMessage(e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.CONFLICT) - public ErrorMessage onConflictException(final ConflictException e) { - log.warn("409 {}", e.getMessage()); - return new ErrorMessage(e.getMessage()); - } - - /** - * Обработка непредвиденного исключения - * - * @param e - исключение - * @return - сообщение об ошибке - */ - @ExceptionHandler - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorMessage handleException(final Exception e) { - log.warn("Error", e); - return new ErrorMessage(e.getMessage()); - } -} diff --git a/src/main/java/ru/practicum/shareit/booking/enums/SearchState.java b/src/main/java/ru/practicum/shareit/booking/enums/SearchState.java deleted file mode 100644 index e65cfe3..0000000 --- a/src/main/java/ru/practicum/shareit/booking/enums/SearchState.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.practicum.shareit.booking.enums; - -/** - * Описание режимов поиска запросов на бронирование - */ -public enum SearchState { - ALL, // Все - CURRENT, // Текущие - PAST, // Завершенные - FUTURE, // Будущие - WAITING, // Ожидающие подтверждения - REJECTED, // Отклоненные -} diff --git a/src/main/java/ru/practicum/shareit/excepton/ConflictException.java b/src/main/java/ru/practicum/shareit/excepton/ConflictException.java deleted file mode 100644 index 6973755..0000000 --- a/src/main/java/ru/practicum/shareit/excepton/ConflictException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.excepton; - -public class ConflictException extends RuntimeException { - public ConflictException(String message) { - super(message); - } -} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java deleted file mode 100644 index 064e2e9..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.shareit.request; - -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/request/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java deleted file mode 100644 index 7b3ed54..0000000 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request.dto; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequestDto { -} diff --git a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java b/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java deleted file mode 100644 index d02c5da..0000000 --- a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.shareit.request.ItemRequest; - -public interface ItemRequestRepository extends JpaRepository { -} diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/src/main/java/ru/practicum/shareit/user/UserMapper.java deleted file mode 100644 index e8f9f68..0000000 --- a/src/main/java/ru/practicum/shareit/user/UserMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.practicum.shareit.user; - -import ru.practicum.shareit.user.dto.UserDto; - -public class UserMapper { - private 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() - ); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 5354097..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,18 +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 - -# TODO Append connection to DB -#spring.datasource.driverClassName -#spring.datasource.url -#spring.datasource.username -#spring.datasource.password -spring.datasource.driverClassName=org.postgresql.Driver -spring.datasource.url=jdbc:postgresql://localhost:5432/shareitdb -spring.datasource.username=test -spring.datasource.password=test \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql deleted file mode 100644 index fcc264a..0000000 --- a/src/main/resources/schema.sql +++ /dev/null @@ -1,51 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL, - CONSTRAINT pk_user PRIMARY KEY (id), - CONSTRAINT UQ_USER_EMAIL UNIQUE (email) -); - -CREATE TABLE IF NOT EXISTS itemrequests ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - description VARCHAR(255) NOT NULL, - user_id BIGINT, - created TIMESTAMP WITHOUT TIME ZONE, - CONSTRAINT pk_itemrequest PRIMARY KEY (id), - CONSTRAINT fk_requests_to_users FOREIGN KEY(user_id) REFERENCES users(id) -); - -CREATE TABLE IF NOT EXISTS items ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - name VARCHAR(255) NOT NULL, - description VARCHAR(255), - owner_id BIGINT NOT NULL, - available BOOLEAN, - request_id BIGINT, - CONSTRAINT pk_item PRIMARY KEY (id), - CONSTRAINT fk_items_to_users FOREIGN KEY(owner_id) REFERENCES users(id), - CONSTRAINT fk_items_to_requests FOREIGN KEY(request_id) REFERENCES itemrequests(id) -); - -CREATE TABLE IF NOT EXISTS bookings ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - time_start TIMESTAMP WITHOUT TIME ZONE NOT NULL, - time_end TIMESTAMP WITHOUT TIME ZONE NOT NULL, - item_id BIGINT NOT NULL, - booker_id BIGINT NOT NULL, - status VARCHAR(16), - CONSTRAINT pk_buoking PRIMARY KEY (id), - CONSTRAINT fk_buokings_to_items FOREIGN KEY(item_id) REFERENCES items(id), - CONSTRAINT fk_buokings_to_users FOREIGN KEY(booker_id) REFERENCES users(id) -); - -CREATE TABLE IF NOT EXISTS comments ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - text VARCHAR(512) NOT NULL, - item_id BIGINT NOT NULL, - author_id BIGINT NOT NULL, - created TIMESTAMP WITHOUT TIME ZONE, - CONSTRAINT pk_comment PRIMARY KEY (id), - CONSTRAINT fk_connemts_to_items FOREIGN KEY(item_id) REFERENCES items(id), - CONSTRAINT fk_connemts_to_users FOREIGN KEY(author_id) REFERENCES users(id) -); \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties deleted file mode 100644 index ba4a8ee..0000000 --- a/src/test/resources/application.properties +++ /dev/null @@ -1,18 +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 - -# TODO Append connection to DB -#spring.datasource.driverClassName -#spring.datasource.url -#spring.datasource.username -#spring.datasource.password -spring.datasource.driverClassName=org.postgresql.Driver -spring.datasource.url=jdbc:postgresql://localhost:5432/shareitdb -spring.datasource.username=test -spring.datasource.password=test diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql deleted file mode 100644 index bd4ded0..0000000 --- a/src/test/resources/schema.sql +++ /dev/null @@ -1,51 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL, - CONSTRAINT pk_user PRIMARY KEY (id), - CONSTRAINT UQ_USER_EMAIL UNIQUE (email) -); - -CREATE TABLE IF NOT EXISTS itemrequests ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - description VARCHAR(255) NOT NULL, - user_id BIGINT, - created TIMESTAMP WITHOUT TIME ZONE, - CONSTRAINT pk_itemrequest PRIMARY KEY (id), - CONSTRAINT fk_requests_to_users FOREIGN KEY(user_id) REFERENCES users(id) -); - -CREATE TABLE IF NOT EXISTS items ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - name VARCHAR(255) NOT NULL, - description VARCHAR(255), - owner_id BIGINT NOT NULL, - available BOOLEAN, - request_id BIGINT, - CONSTRAINT pk_item PRIMARY KEY (id), - CONSTRAINT fk_items_to_users FOREIGN KEY(owner_id) REFERENCES users(id), - CONSTRAINT fk_items_to_requests FOREIGN KEY(request_id) REFERENCES itemrequests(id) -); - -CREATE TABLE IF NOT EXISTS bookings ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - time_start TIMESTAMP WITHOUT TIME ZONE NOT NULL, - time_end TIMESTAMP WITHOUT TIME ZONE NOT NULL, - item_id BIGINT NOT NULL, - booker_id BIGINT NOT NULL, - status VARCHAR(16), - CONSTRAINT pk_buoking PRIMARY KEY (id), - CONSTRAINT fk_buokings_to_items FOREIGN KEY(item_id) REFERENCES items(id), - CONSTRAINT fk_buokings_to_users FOREIGN KEY(booker_id) REFERENCES users(id) -); - -CREATE TABLE IF NOT EXISTS comments ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - text VARCHAR(512) NOT NULL, - item_id BIGINT NOT NULL, - author_id BIGINT NOT NULL, - created TIMESTAMP WITHOUT TIME ZONE, - CONSTRAINT pk_comment PRIMARY KEY (id), - CONSTRAINT fk_connemts_to_items FOREIGN KEY(item_id) REFERENCES items(id), - CONSTRAINT fk_connemts_to_users FOREIGN KEY(author_id) REFERENCES users(id) -); \ No newline at end of file