diff --git a/.run/ShareItGateway.run.xml b/.run/ShareItGateway.run.xml new file mode 100644 index 0000000..32c8129 --- /dev/null +++ b/.run/ShareItGateway.run.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.run/ShareItServer.run.xml b/.run/ShareItServer.run.xml new file mode 100644 index 0000000..a8ed9e5 --- /dev/null +++ b/.run/ShareItServer.run.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index ab7c00d..0000000 --- a/compose.yaml +++ /dev/null @@ -1,17 +0,0 @@ -services: - db: - image: postgres:16.1 - container_name: postgres - ports: - - "5432:5432" - volumes: - - .docker/data/postgres:/var/lib/postgresql/data/ - environment: - - POSTGRES_DB=shareit - - POSTGRES_USER=shareit - - POSTGRES_PASSWORD=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/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..381d778 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,41 @@ +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: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=shareit + - POSTGRES_USER=shareit + - POSTGRES_DB=shareit + volumes: + - .docker/data/postgres:/var/lib/postgresql/data/ + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 \ No newline at end of file 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/src/main/java/ru/practicum/shareit/ShareItApp.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java similarity index 72% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java index a10a87d..a1dbcae 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -4,10 +4,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ShareItApp { +public class ShareItGateway { public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); + SpringApplication.run(ShareItGateway.class, args); } } 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..e30966a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.booking; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.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; + +@Slf4j +@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 getBookings( + long userId, + BookingState state + ) { + log.info("Get bookings by user: {}, state: {}", userId, state); + return get("?state=" + state, userId); + } + + public ResponseEntity bookItem(long userId, BookItemRequestDto requestDto) { + log.info("Booking by user {} and request: {}", userId, requestDto); + return post("", userId, requestDto); + } + + public ResponseEntity approve(Long bookingId, Long ownerId, boolean approved) { + log.info("Approve booking by bookingId: {}, ownerId: {}", bookingId, ownerId); + return patch("/" + bookingId + "?approved=" + approved, ownerId, null, null); + } + + public ResponseEntity getBooking(long userId, Long bookingId) { + log.info("Get booking by user {} and bookingId: {}", userId, bookingId); + return get("/" + bookingId, userId); + } + + public ResponseEntity getByOwnerAndState(Long ownerId, BookingState state) { + log.info("Get booking by owner and state: {}", state); + return get("/owner?state=" + state, ownerId); + } + +} 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..f0b79d2 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,81 @@ +package ru.practicum.shareit.booking; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import ru.practicum.shareit.booking.dto.BookItemRequestDto; +import ru.practicum.shareit.booking.dto.BookingState; + +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Validated +public class BookingController { + + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + private final BookingClient bookingClient; + + @GetMapping + public ResponseEntity getBookings( + @RequestHeader(HEADER_USER_ID) long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam + ) { + BookingState state = getBookingState(stateParam); + + return bookingClient.getBookings(userId, state); + } + + @PostMapping + public ResponseEntity bookItem( + @RequestHeader(HEADER_USER_ID) long userId, + @RequestBody @Valid BookItemRequestDto requestDto + ) { + return bookingClient.bookItem(userId, requestDto); + } + + @PatchMapping("/{bookingId}") + public ResponseEntity approve( + @PathVariable Long bookingId, + @RequestParam boolean approved, + @RequestHeader(name = HEADER_USER_ID) Long ownerId + ) { + return bookingClient.approve(bookingId, ownerId, approved); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getBooking( + @RequestHeader(HEADER_USER_ID) long userId, + @PathVariable Long bookingId + ) { + return bookingClient.getBooking(userId, bookingId); + } + + @GetMapping("/owner") + public ResponseEntity getByOwnerAndState( + @RequestParam(required = false, defaultValue = "all") String stateParam, + @RequestHeader(name = HEADER_USER_ID) Long ownerId + ) { + BookingState state = getBookingState(stateParam); + + return bookingClient.getByOwnerAndState(ownerId, state); + } + + private static BookingState getBookingState(String stateParam) { + return BookingState.from(stateParam).orElseThrow( + () -> new IllegalArgumentException("Unknown state: " + stateParam) + ); + } + +} 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..c733f0f --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookItemRequestDto.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.booking.dto; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import ru.practicum.shareit.booking.validator.StartDateIsBeforeEnd; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@StartDateIsBeforeEnd +public class BookItemRequestDto { + + private Long itemId; + + @NotNull + @FutureOrPresent + private LocalDateTime start; + + @NotNull + @Future + private LocalDateTime end; + +} 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..135de62 --- /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(); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/validator/DateValidator.java b/gateway/src/main/java/ru/practicum/shareit/booking/validator/DateValidator.java new file mode 100644 index 0000000..8a29756 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/validator/DateValidator.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.booking.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import ru.practicum.shareit.booking.dto.BookItemRequestDto; + +import java.time.LocalDateTime; + +public class DateValidator + implements ConstraintValidator { + + @Override + public boolean isValid(BookItemRequestDto bookItemRequestDto, ConstraintValidatorContext constraintValidatorContext) { + LocalDateTime start = bookItemRequestDto.getStart(); + LocalDateTime end = bookItemRequestDto.getEnd(); + + return start != null + && end != null + && start.isBefore(end); + } + +} diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/validator/StartDateIsBeforeEnd.java b/gateway/src/main/java/ru/practicum/shareit/booking/validator/StartDateIsBeforeEnd.java new file mode 100644 index 0000000..f2b5162 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/validator/StartDateIsBeforeEnd.java @@ -0,0 +1,22 @@ +package ru.practicum.shareit.booking.validator; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Constraint(validatedBy = DateValidator.class) +public @interface StartDateIsBeforeEnd { + + String message() default "Start must be before end"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} 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..3b0c159 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,124 @@ +package ru.practicum.shareit.client; + +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +public class BaseClient { + + protected final RestTemplate rest; + + public BaseClient(RestTemplate rest) { + this.rest = rest; + } + + protected ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + protected ResponseEntity get(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return put(path, userId, null, body); + } + + protected ResponseEntity put(String path, long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId) { + return patch(path, userId, null, null); + } + + protected ResponseEntity patch(String path, long userId, T body) { + return patch(path, userId, null, body); + } + + protected ResponseEntity patch(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + } + + protected ResponseEntity delete(String path) { + return delete(path, null, null); + } + + protected ResponseEntity delete(String path, long userId) { + return delete(path, userId, null); + } + + protected ResponseEntity delete(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + } + + private ResponseEntity makeAndSendRequest( + HttpMethod method, String path, Long userId, @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); + + ResponseEntity shareitServerResponse; + try { + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareGatewayResponse(shareitServerResponse); + } + + private HttpHeaders defaultHeaders(Long userId) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + if (userId != null) { + headers.set("X-Sharer-User-Id", String.valueOf(userId)); + } + return headers; + } + + private static ResponseEntity prepareGatewayResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + + return responseBuilder.build(); + } + +} diff --git a/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java new file mode 100644 index 0000000..e0c8449 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.exception; + +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; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleOtherException(final Throwable e) { + log.error("Gateway error 500: " + e.getMessage()); + return new ErrorResponse(e.getMessage()); + } + +} diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ErrorResponse.java rename to gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java 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..fc531dc --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -0,0 +1,66 @@ +package ru.practicum.shareit.item; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.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.CommentCreateDto; +import ru.practicum.shareit.item.dto.ItemCreateDto; +import ru.practicum.shareit.item.dto.ItemUpdateDto; + +import java.util.Map; + +@Slf4j +@Service +public class ItemClient extends BaseClient { + + private static final String API_PREFIX = "/items"; + + @Autowired + public ItemClient( + @Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder + ) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity create(ItemCreateDto dto, Long userId) { + log.info("Create item {} and user {}", dto, userId); + return post("", userId, dto); + } + + public ResponseEntity update(ItemUpdateDto dto, Long itemId, Long userId) { + log.info("Update item {} and user {}", dto, userId); + return patch("/" + itemId, userId, dto); + } + + public ResponseEntity findItemById(Long itemId, Long userId) { + log.info("Find item {} and user {}", itemId, userId); + return get("/" + itemId, userId); + } + + public ResponseEntity findItemsByOwner(Long ownerId) { + log.info("Find items by owner {}", ownerId); + return get("/owner", ownerId); + } + + public ResponseEntity findItemsByText(String text) { + Map params = Map.of("text", text); + return get("/search", null, params); + } + + public ResponseEntity addComment(Long itemId, Long authorId, CommentCreateDto commentDto) { + return post("/" + itemId + "/comment", authorId, 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..df6f290 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,74 @@ +package ru.practicum.shareit.item; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.practicum.shareit.item.dto.CommentCreateDto; +import ru.practicum.shareit.item.dto.ItemCreateDto; +import ru.practicum.shareit.item.dto.ItemUpdateDto; + +@Controller +@RequestMapping(path = "/items") +@RequiredArgsConstructor +@Validated +public class ItemController { + + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + private final ItemClient itemClient; + + @PostMapping + public ResponseEntity create( + @Valid @RequestBody ItemCreateDto dto, + @RequestHeader(name = HEADER_USER_ID) Long userId + ) { + return itemClient.create(dto, userId); + } + + @PatchMapping("/{itemId}") + public ResponseEntity update( + @Valid @RequestBody ItemUpdateDto dto, + @PathVariable Long itemId, + @RequestHeader(name = HEADER_USER_ID) Long userId + ) { + return itemClient.update(dto, itemId, userId); + } + + @GetMapping("/{itemId}") + public ResponseEntity getItem( + @PathVariable Long itemId, + @RequestHeader(name = HEADER_USER_ID) Long userId + ) { + return itemClient.findItemById(itemId, userId); + } + + @GetMapping + public ResponseEntity getItemsByOwner(@RequestHeader(name = HEADER_USER_ID) Long ownerId) { + return itemClient.findItemsByOwner(ownerId); + } + + @GetMapping("/search") + public ResponseEntity findItemsByText(@RequestParam String text) { + return itemClient.findItemsByText(text); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity addComment( + @PathVariable Long itemId, + @RequestHeader(name = HEADER_USER_ID) Long authorId, + @RequestBody @Valid CommentCreateDto commentDto + ) { + return itemClient.addComment(itemId, authorId, commentDto); + } + +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java similarity index 81% rename from src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java index 3a788e3..d698785 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java @@ -1,6 +1,6 @@ package ru.practicum.shareit.item.dto; -import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -12,7 +12,7 @@ @Builder public class CommentCreateDto { - @NotBlank + @NotNull private String text; } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java similarity index 75% rename from src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java index 82d567d..7627076 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java @@ -1,13 +1,16 @@ package ru.practicum.shareit.item.dto; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data @AllArgsConstructor +@NoArgsConstructor @Builder public class ItemCreateDto { @@ -20,4 +23,7 @@ public class ItemCreateDto { @NotNull private Boolean available; + @Nullable + private Long requestId; + } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java similarity index 87% rename from src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java index 874ba5f..65063f3 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java @@ -4,14 +4,14 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data @AllArgsConstructor +@NoArgsConstructor @Builder public class ItemUpdateDto { - private Long id; - @Nullable private String name; 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..062bdc1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java @@ -0,0 +1,53 @@ +package ru.practicum.shareit.request; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.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.ItemRequestCreateDto; + +@Slf4j +@Service +public class ItemRequestClient extends BaseClient { + + private static final String API_PREFIX = "/requests"; + + @Autowired + public ItemRequestClient( + @Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder + ) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity create(Long userId, ItemRequestCreateDto itemRequestCreateDto) { + log.info("Create itemRequest for userId={}, dto: {}", userId, itemRequestCreateDto); + return post("", userId, itemRequestCreateDto); + } + + public ResponseEntity getListByUser(Long userId) { + log.info("Get itemRequests for userId={}", userId); + return get("", userId); + } + + public ResponseEntity getList(Long userId) { + log.info("Get all itemRequests for userId={}", userId); + return get("/all", userId); + } + + public ResponseEntity getById(Long requestId) { + log.info("Get itemRequest for requestId={}", requestId); + return get("/" + requestId); + } + +} 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..9473f84 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,49 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.practicum.shareit.request.dto.ItemRequestCreateDto; + +@Controller +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +@Validated +public class ItemRequestController { + + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + private final ItemRequestClient itemRequestClient; + + @PostMapping + public ResponseEntity createItem( + @RequestHeader(HEADER_USER_ID) Long userId, + @Valid @RequestBody ItemRequestCreateDto itemRequestCreateDto + ) { + return itemRequestClient.create(userId, itemRequestCreateDto); + } + + @GetMapping + public ResponseEntity getListByUser(@RequestHeader(name = HEADER_USER_ID) Long userId) { + return itemRequestClient.getListByUser(userId); + } + + @GetMapping("/all") + public ResponseEntity getList(@RequestHeader(name = HEADER_USER_ID) Long userId) { + return itemRequestClient.getList(userId); + } + + @GetMapping("/{requestId}") + public ResponseEntity getById(@PathVariable Long requestId) { + return itemRequestClient.getById(requestId); + } + +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestCreateDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestCreateDto.java new file mode 100644 index 0000000..17be3b1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestCreateDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ItemRequestCreateDto { + + @NotBlank + private String description; + +} 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..dbc7f38 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -0,0 +1,54 @@ +package ru.practicum.shareit.user; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.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.UserCreateDto; +import ru.practicum.shareit.user.dto.UserUpdateDto; + +@Slf4j +@Service +public class UserClient extends BaseClient { + + private static final String API_PREFIX = "/users"; + + @Autowired + public UserClient( + @Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder + ) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity create(UserCreateDto dto) { + log.info("Create request for user={}", dto); + return post("", dto); + } + + public ResponseEntity update(Long userId, UserUpdateDto dto) { + log.info("Update request for user={}, dto: {}", userId, dto); + return patch("/" + userId, dto); + } + + public ResponseEntity deleteById(Long userId) { + log.info("Delete request for user={}", userId); + return delete("/" + userId); + } + + public ResponseEntity findById(Long userId) { + log.info("Find request for user={}", userId); + return get("/" + userId); + } + +} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserController.java b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java new file mode 100644 index 0000000..c2f9c27 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,49 @@ +package ru.practicum.shareit.user; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.practicum.shareit.user.dto.UserCreateDto; +import ru.practicum.shareit.user.dto.UserUpdateDto; + +@Controller +@RequestMapping(path = "/users") +@RequiredArgsConstructor +@Validated +public class UserController { + + private final UserClient userClient; + + @PostMapping + public ResponseEntity create(@Valid @RequestBody UserCreateDto dto) { + return userClient.create(dto); + } + + @PatchMapping("/{userId}") + public ResponseEntity update( + @Valid @RequestBody UserUpdateDto dto, + @PathVariable Long userId + ) { + return userClient.update(userId, dto); + } + + @DeleteMapping("/{userId}") + public ResponseEntity delete(@PathVariable Long userId) { + return userClient.deleteById(userId); + } + + @GetMapping("/{userId}") + public ResponseEntity findById(@PathVariable Long userId) { + return userClient.findById(userId); + } + +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserCreateDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserCreateDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserCreateDto.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/UserCreateDto.java diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserUpdateDto.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDto.java diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..2ee0851 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,7 @@ +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 14274a6..79cba37 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ ru.practicum shareit + pom 0.0.1-SNAPSHOT ShareIt @@ -19,76 +20,29 @@ 21 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-data-jpa - - - 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 - - + + gateway + server + - - - 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 @@ -235,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..89546a4 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,91 @@ + + + 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 + + + jakarta.validation + jakarta.validation-api + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + + + diff --git a/server/src/main/java/ru/practicum/shareit/ShareItServer.java b/server/src/main/java/ru/practicum/shareit/ShareItServer.java new file mode 100644 index 0000000..303541d --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/ShareItServer.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItServer { + + public static void main(String[] args) { + SpringApplication.run(ShareItServer.class, args); + } + +} 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 08c024b..35b5bc9 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.booking; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -23,14 +22,14 @@ @RequiredArgsConstructor public class BookingController { - public static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + public static final String HEADER_USER_ID = "X-Sharer-User-Id"; private final BookingServiceInterface bookingService; @PostMapping public BookingDto create( - final @RequestBody @Valid BookingCreateDto bookingDto, - final @RequestHeader(name = X_SHARER_USER_ID) Long bookerId + final @RequestBody BookingCreateDto bookingDto, + final @RequestHeader(name = HEADER_USER_ID) Long bookerId ) { bookingDto.setBookerId(bookerId); @@ -41,7 +40,7 @@ public BookingDto create( public BookingDto approve( final @PathVariable Long bookingId, final @RequestParam boolean approved, - final @RequestHeader(name = X_SHARER_USER_ID) Long ownerId + final @RequestHeader(name = HEADER_USER_ID) Long ownerId ) { return bookingService.approve(bookingId, ownerId, approved); } @@ -49,7 +48,7 @@ public BookingDto approve( @GetMapping("/{bookingId}") public BookingDto getById( final @PathVariable Long bookingId, - final @RequestHeader(name = X_SHARER_USER_ID) Long userId + final @RequestHeader(name = HEADER_USER_ID) Long userId ) { return bookingService.getById(bookingId, userId); } @@ -57,7 +56,7 @@ public BookingDto getById( @GetMapping public List getByBookerAndState( final @RequestParam(required = false) BookingState state, - final @RequestHeader(name = X_SHARER_USER_ID) Long bookerId + final @RequestHeader(name = HEADER_USER_ID) Long bookerId ) { return bookingService.getByBookerAndState( bookerId, @@ -68,7 +67,7 @@ public List getByBookerAndState( @GetMapping("/owner") public List getByOwnerAndState( final @RequestParam(required = false) BookingState state, - final @RequestHeader(name = X_SHARER_USER_ID) Long ownerId + final @RequestHeader(name = HEADER_USER_ID) Long ownerId ) { return bookingService.getByOwnerAndState(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/src/main/java/ru/practicum/shareit/booking/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/BookingService.java similarity index 92% rename from src/main/java/ru/practicum/shareit/booking/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingService.java index dba9962..61d4dda 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingService.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingService.java @@ -41,7 +41,7 @@ public class BookingService implements BookingServiceInterface { @Override @Transactional public BookingDto create(final BookingCreateDto bookingDto) { - log.info("Creating booking. ItemId - '{}', BookerId - '{}'", bookingDto.getItemId(), bookingDto.getBookerId()); + log.info("Create booking={}", bookingDto); Item item = itemRepository.findById(bookingDto.getItemId()).orElseThrow( () -> new NotFoundException("Item with id='%d' not found".formatted(bookingDto.getItemId())) @@ -71,7 +71,7 @@ public BookingDto create(final BookingCreateDto bookingDto) { @Override @Transactional public BookingDto approve(final Long id, final Long ownerId, final boolean approved) { - log.info("Approve booking with id='{}' and ownerId='{}'", id, ownerId); + log.info("Approve booking={}, owner: {}", id, ownerId); Booking booking = bookingRepository.findById(id).orElseThrow( () -> new NotFoundException(BOOKING_NOT_FOUND.formatted(id)) @@ -94,7 +94,7 @@ public BookingDto approve(final Long id, final Long ownerId, final boolean appro @Override public BookingDto getById(final Long id, final Long userId) { - log.info("Get booking with id '{}' from user with id '{}'", id, userId); + log.info("Get booking={}, userId: {}", id, userId); Booking booking = bookingRepository.findById(id).orElseThrow( () -> new NotFoundException(BOOKING_NOT_FOUND.formatted(id)) @@ -112,7 +112,7 @@ public BookingDto getById(final Long id, final Long userId) { @Override public List getByBookerAndState(final Long bookerId, final BookingState state) { - log.info("getByBookerAndState: {}, {}", bookerId, state); + log.info("Get bookings by booker={}, state: {}", bookerId, state); User booker = userRepository.findById(bookerId).orElseThrow( () -> new NotFoundException(USER_NOT_FOUND.formatted(bookerId)) @@ -139,8 +139,7 @@ public List getByBookerAndState(final Long bookerId, final BookingSt } @Override - public List getByOwnerAndState(Long ownerId, BookingState state) { - log.info("getByOwnerAndState: {}, {}", ownerId, state); + public List getByOwnerAndState(final Long ownerId, final BookingState state) { User owner = userRepository.findById(ownerId).orElseThrow( () -> new NotFoundException(USER_NOT_FOUND.formatted(ownerId)) diff --git a/src/main/java/ru/practicum/shareit/booking/contracts/BookingRepositoryInterface.java b/server/src/main/java/ru/practicum/shareit/booking/contracts/BookingRepositoryInterface.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/contracts/BookingRepositoryInterface.java rename to server/src/main/java/ru/practicum/shareit/booking/contracts/BookingRepositoryInterface.java diff --git a/src/main/java/ru/practicum/shareit/booking/contracts/BookingServiceInterface.java b/server/src/main/java/ru/practicum/shareit/booking/contracts/BookingServiceInterface.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/contracts/BookingServiceInterface.java rename to server/src/main/java/ru/practicum/shareit/booking/contracts/BookingServiceInterface.java diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java new file mode 100644 index 0000000..4783123 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BookingCreateDto { + + private LocalDateTime start; + + private LocalDateTime end; + + private Long itemId; + + private Long bookerId; +} diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/Booking.java rename to server/src/main/java/ru/practicum/shareit/booking/model/Booking.java diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingState.java b/server/src/main/java/ru/practicum/shareit/booking/model/BookingState.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/BookingState.java rename to server/src/main/java/ru/practicum/shareit/booking/model/BookingState.java diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java rename to server/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java diff --git a/src/main/java/ru/practicum/shareit/exception/EmptyIdException.java b/server/src/main/java/ru/practicum/shareit/exception/EmptyIdException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/EmptyIdException.java rename to server/src/main/java/ru/practicum/shareit/exception/EmptyIdException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java similarity index 74% rename from src/main/java/ru/practicum/shareit/exception/ErrorHandler.java rename to server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java index cf7b905..4d99124 100644 --- a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java +++ b/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -10,10 +10,12 @@ @RestControllerAdvice public class ErrorHandler { + private static final String ERROR_LOG_MESSAGE = "Server error ({}): {}"; + @ExceptionHandler @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFoundException(final NotFoundException e) { - log.warn(e.getMessage()); + log.warn(ERROR_LOG_MESSAGE, HttpStatus.NOT_FOUND, e.getMessage()); return new ErrorResponse(e.getMessage()); } @@ -21,7 +23,7 @@ public ErrorResponse handleNotFoundException(final NotFoundException e) { @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleNotFoundException(final EmptyIdException e) { - log.warn(e.getMessage()); + log.warn(ERROR_LOG_MESSAGE, HttpStatus.BAD_REQUEST, e.getMessage()); return new ErrorResponse(e.getMessage()); } @@ -29,7 +31,7 @@ public ErrorResponse handleNotFoundException(final EmptyIdException e) { @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleNotFoundException(final InvalidOwnerException e) { - log.warn(e.getMessage()); + log.warn(ERROR_LOG_MESSAGE, HttpStatus.BAD_REQUEST, e.getMessage()); return new ErrorResponse(e.getMessage()); } @@ -37,7 +39,7 @@ public ErrorResponse handleNotFoundException(final InvalidOwnerException e) { @ExceptionHandler @ResponseStatus(HttpStatus.CONFLICT) public ErrorResponse handleNotFoundException(final NotUniqueEmailException e) { - log.warn(e.getMessage()); + log.warn(ERROR_LOG_MESSAGE, HttpStatus.CONFLICT, e.getMessage()); return new ErrorResponse(e.getMessage()); } @@ -45,7 +47,7 @@ public ErrorResponse handleNotFoundException(final NotUniqueEmailException e) { @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleOtherException(final Throwable e) { - log.warn(e.getMessage()); + log.warn(ERROR_LOG_MESSAGE, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); return new ErrorResponse(e.getMessage()); } @@ -53,7 +55,7 @@ public ErrorResponse handleOtherException(final Throwable e) { @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleOtherException(final InvalidBookingStatusException e) { - log.warn(e.getMessage()); + log.warn(ERROR_LOG_MESSAGE, HttpStatus.BAD_REQUEST, e.getMessage()); return new ErrorResponse(e.getMessage()); } @@ -61,7 +63,7 @@ public ErrorResponse handleOtherException(final InvalidBookingStatusException e) @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleOtherException(final UserDoesNotHaveBookedItem e) { - log.warn(e.getMessage()); + log.warn(ERROR_LOG_MESSAGE, HttpStatus.BAD_REQUEST, e.getMessage()); return new ErrorResponse(e.getMessage()); } diff --git a/server/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java b/server/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java new file mode 100644 index 0000000..53517ae --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ErrorResponse { + + private final String error; + +} diff --git a/src/main/java/ru/practicum/shareit/exception/InvalidBookingStatusException.java b/server/src/main/java/ru/practicum/shareit/exception/InvalidBookingStatusException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/InvalidBookingStatusException.java rename to server/src/main/java/ru/practicum/shareit/exception/InvalidBookingStatusException.java diff --git a/src/main/java/ru/practicum/shareit/exception/InvalidOwnerException.java b/server/src/main/java/ru/practicum/shareit/exception/InvalidOwnerException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/InvalidOwnerException.java rename to server/src/main/java/ru/practicum/shareit/exception/InvalidOwnerException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ItemUnavailableException.java b/server/src/main/java/ru/practicum/shareit/exception/ItemUnavailableException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ItemUnavailableException.java rename to server/src/main/java/ru/practicum/shareit/exception/ItemUnavailableException.java diff --git a/src/main/java/ru/practicum/shareit/exception/NotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/NotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/NotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/NotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exception/NotUniqueEmailException.java b/server/src/main/java/ru/practicum/shareit/exception/NotUniqueEmailException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/NotUniqueEmailException.java rename to server/src/main/java/ru/practicum/shareit/exception/NotUniqueEmailException.java diff --git a/src/main/java/ru/practicum/shareit/exception/UserDoesNotHaveBookedItem.java b/server/src/main/java/ru/practicum/shareit/exception/UserDoesNotHaveBookedItem.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/UserDoesNotHaveBookedItem.java rename to server/src/main/java/ru/practicum/shareit/exception/UserDoesNotHaveBookedItem.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 78% rename from src/main/java/ru/practicum/shareit/item/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/ItemController.java index b27b4ad..8c5f488 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.item; -import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -27,22 +26,22 @@ @AllArgsConstructor public class ItemController { - public static final String X_SHARER_USER_ID = "X-Sharer-User-Id"; + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; private ItemServiceInterface itemService; @PostMapping public ItemDto create( - final @Valid @RequestBody ItemCreateDto dto, - final @RequestHeader(name = X_SHARER_USER_ID) Long userId + final @RequestBody ItemCreateDto dto, + final @RequestHeader(name = HEADER_USER_ID) Long userId ) { return itemService.create(dto, userId); } @PatchMapping("/{itemId}") public ItemDto update( - final @Valid @RequestBody ItemUpdateDto dto, + final @RequestBody ItemUpdateDto dto, final @PathVariable Long itemId, - final @RequestHeader(name = X_SHARER_USER_ID) Long userId + final @RequestHeader(name = HEADER_USER_ID) Long userId ) { if (itemId == null) { throw new EmptyIdException("Id required"); @@ -56,13 +55,13 @@ public ItemDto update( @GetMapping("/{itemId}") public ItemInfoDto getItem( final @PathVariable Long itemId, - final @RequestHeader(name = X_SHARER_USER_ID) Long userId + final @RequestHeader(name = HEADER_USER_ID) Long userId ) { return itemService.findItemById(itemId, userId); } @GetMapping - public List getItemsByOwner(final @RequestHeader(name = X_SHARER_USER_ID) Long ownerId) { + public List getItemsByOwner(final @RequestHeader(name = HEADER_USER_ID) Long ownerId) { return itemService.findItemsByOwner(ownerId); } @@ -74,8 +73,8 @@ public List findItemsByText(final @RequestParam String text) { @PostMapping("/{itemId}/comment") public CommentDto addComment( final @PathVariable Long itemId, - final @RequestHeader(name = X_SHARER_USER_ID) Long authorId, - final @RequestBody @Valid CommentCreateDto commentDto + final @RequestHeader(name = HEADER_USER_ID) Long authorId, + final @RequestBody CommentCreateDto commentDto ) { return itemService.addComment(itemId, authorId, commentDto); } diff --git a/src/main/java/ru/practicum/shareit/item/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/ItemService.java similarity index 89% rename from src/main/java/ru/practicum/shareit/item/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/ItemService.java index b4ec145..9c53b61 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemService.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemService.java @@ -25,6 +25,8 @@ import ru.practicum.shareit.item.mapper.ItemMapper; import ru.practicum.shareit.item.model.Comment; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.contracts.ItemRequestRepositoryInterface; +import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.user.contracts.UserRepositoryInterface; import ru.practicum.shareit.user.model.User; @@ -32,7 +34,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; @@ -49,24 +50,37 @@ public class ItemService implements ItemServiceInterface { private final UserRepositoryInterface userRepository; private final CommentRepositoryInterface commentRepository; private final BookingRepositoryInterface bookingRepository; + private final ItemRequestRepositoryInterface itemRequestRepositoryInterface; private final Sort commentsSort = Sort.by(Sort.Direction.DESC, "created"); private final Sort bookingOrder = Sort.by(Sort.Direction.ASC, "start"); @Override public ItemDto create(final ItemCreateDto itemDto, final Long userId) { - log.info("Creating Item: {}, userId - '{}'", itemDto.getName(), userId); + log.info("Create item {} by user {}", itemDto, userId); User user = userRepository.findById(userId).orElseThrow( () -> new NotFoundException(USER_NOT_FOUND.formatted(userId)) ); + ItemRequest itemRequest = null; + + if (itemDto.getRequestId() != null) { + itemRequest = itemRequestRepositoryInterface.findById(itemDto.getRequestId()).orElseThrow( + () -> new NotFoundException("Item request with id='%d' not found".formatted(itemDto.getRequestId())) + ); + } + Item item = new Item(); item.setName(itemDto.getName()); item.setDescription(itemDto.getDescription()); item.setOwner(user); item.setAvailable(itemDto.getAvailable()); + if (itemRequest != null) { + item.setRequest(itemRequest); + } + Item newItem = itemRepository.save(item); return ItemMapper.toItemDto(newItem); @@ -74,7 +88,7 @@ public ItemDto create(final ItemCreateDto itemDto, final Long userId) { @Override public ItemDto update(final ItemUpdateDto itemDto, final Long userId) { - log.info("Updating Item: {}, userId - '{}'", itemDto.getId(), userId); + log.info("Update item {} by user {}", itemDto, userId); if (itemDto.getId() == null) { throw new EmptyIdException("Item id is empty"); @@ -88,7 +102,7 @@ public ItemDto update(final ItemUpdateDto itemDto, final Long userId) { () -> new NotFoundException(ITEM_NOT_FOUND.formatted(itemDto.getId())) ); - if (!Objects.equals(item.getOwner(), user)) { + if (!item.getOwner().getId().equals(user.getId())) { throw new InvalidOwnerException("Owner is not the same user"); } @@ -112,7 +126,7 @@ public ItemDto update(final ItemUpdateDto itemDto, final Long userId) { @Override @Transactional(readOnly = true) public ItemInfoDto findItemById(Long itemId, Long userId) { - log.info("Finding Item with id: '{}'", itemId); + log.info("Find item by id {} and user {}", itemId, userId); Item item = itemRepository.findById(itemId).orElseThrow( () -> new NotFoundException(ITEM_NOT_FOUND.formatted(itemId)) @@ -141,7 +155,7 @@ public ItemInfoDto findItemById(Long itemId, Long userId) { @Override @Transactional(readOnly = true) public List findItemsByOwner(final Long ownerId) { - log.info("Finding Items by owner with id: '{}'", ownerId); + log.info("Find items by owner {}", ownerId); User user = userRepository.findById(ownerId).orElseThrow( () -> new NotFoundException(USER_NOT_FOUND.formatted(ownerId)) @@ -186,7 +200,7 @@ public List findItemsByOwner(final Long ownerId) { @Override @Transactional(readOnly = true) public List findItemsByText(final String text) { - log.info("Finding Items by text: '{}'", text); + log.info("Find items by text {}", text); if (text == null || text.isEmpty()) { return Collections.emptyList(); @@ -201,7 +215,7 @@ public List findItemsByText(final String text) { @Override public CommentDto addComment(final Long itemId, final Long authorId, final CommentCreateDto commentDto) { - log.info("Add comment: itemId - '{}', authorId - '{}'", itemId, authorId); + log.info("Add comment {} for item {} by user {}", commentDto, itemId, authorId); Item item = itemRepository.findById(itemId).orElseThrow( () -> new NotFoundException(ITEM_NOT_FOUND.formatted(itemId)) diff --git a/src/main/java/ru/practicum/shareit/item/contracts/CommentRepositoryInterface.java b/server/src/main/java/ru/practicum/shareit/item/contracts/CommentRepositoryInterface.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/contracts/CommentRepositoryInterface.java rename to server/src/main/java/ru/practicum/shareit/item/contracts/CommentRepositoryInterface.java diff --git a/src/main/java/ru/practicum/shareit/item/contracts/ItemRepositoryInterface.java b/server/src/main/java/ru/practicum/shareit/item/contracts/ItemRepositoryInterface.java similarity index 92% rename from src/main/java/ru/practicum/shareit/item/contracts/ItemRepositoryInterface.java rename to server/src/main/java/ru/practicum/shareit/item/contracts/ItemRepositoryInterface.java index 9013bc0..efa01fa 100644 --- a/src/main/java/ru/practicum/shareit/item/contracts/ItemRepositoryInterface.java +++ b/server/src/main/java/ru/practicum/shareit/item/contracts/ItemRepositoryInterface.java @@ -21,4 +21,6 @@ public interface ItemRepositoryInterface extends JpaRepository { ) List findAllByText(String text); + List findAllByRequest_IdOrderByIdDesc(Long id); + } diff --git a/src/main/java/ru/practicum/shareit/item/contracts/ItemServiceInterface.java b/server/src/main/java/ru/practicum/shareit/item/contracts/ItemServiceInterface.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/contracts/ItemServiceInterface.java rename to server/src/main/java/ru/practicum/shareit/item/contracts/ItemServiceInterface.java diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java new file mode 100644 index 0000000..eb67a0d --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentCreateDto.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CommentCreateDto { + + private String text; + +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/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 server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java new file mode 100644 index 0000000..1a3f667 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemCreateDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@AllArgsConstructor +@Builder +public class ItemCreateDto { + + private String name; + + private String description; + + private Boolean available; + + private Long requestId; + +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java similarity index 68% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index 5fbf136..624f604 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -1,7 +1,5 @@ package ru.practicum.shareit.item.dto; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,13 +11,10 @@ public class ItemDto { private Long id; - @NotBlank private String name; - @NotBlank private String description; - @NotNull private Boolean available; } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemInfoDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemInfoDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/ItemInfoDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemInfoDto.java diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java new file mode 100644 index 0000000..aa49ced --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemUpdateDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@AllArgsConstructor +@Builder +public class ItemUpdateDto { + + private Long id; + + private String name; + + private String description; + + private Boolean available; + +} diff --git a/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java rename to server/src/main/java/ru/practicum/shareit/item/mapper/CommentMapper.java diff --git a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java 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 100% 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 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 100% 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 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..4bb4ede --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.shareit.request.contracts.ItemRequestServiceInterface; +import ru.practicum.shareit.request.dto.ItemRequestCreateDto; +import ru.practicum.shareit.request.dto.ItemRequestResponseDto; + +import java.util.List; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + + private static final String HEADER_USER_ID = "X-Sharer-User-Id"; + + private final ItemRequestServiceInterface itemRequestService; + + @PostMapping + public ItemRequestResponseDto createItem( + final @RequestHeader(HEADER_USER_ID) Long userId, + final @RequestBody ItemRequestCreateDto itemRequestCreateDto + ) { + itemRequestCreateDto.setUserId(userId); + + return itemRequestService.create(itemRequestCreateDto); + } + + @GetMapping + public List getListByUser(final @RequestHeader(name = HEADER_USER_ID) Long userId) { + return itemRequestService.getListByUser(userId); + } + + @GetMapping("/all") + public List getList(final @RequestHeader(name = HEADER_USER_ID) Long userId) { + return itemRequestService.getList(userId); + } + + @GetMapping("/{requestId}") + public ItemRequestResponseDto getById(final @PathVariable Long requestId) { + return itemRequestService.getById(requestId); + } + +} diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java new file mode 100644 index 0000000..7611f6f --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java @@ -0,0 +1,89 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.contracts.ItemRepositoryInterface; +import ru.practicum.shareit.request.contracts.ItemRequestRepositoryInterface; +import ru.practicum.shareit.request.contracts.ItemRequestServiceInterface; +import ru.practicum.shareit.request.dto.ItemRequestCreateDto; +import ru.practicum.shareit.request.dto.ItemRequestResponseDto; +import ru.practicum.shareit.request.mapper.ItemRequestMapper; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.contracts.UserRepositoryInterface; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ItemRequestService implements ItemRequestServiceInterface { + + private final ItemRequestRepositoryInterface itemRequestRepository; + private final UserRepositoryInterface userRepository; + private final ItemRepositoryInterface itemRepository; + + @Override + public ItemRequestResponseDto create(final ItemRequestCreateDto itemRequestCreateDto) { + log.info("Create itemRequest request: {}", itemRequestCreateDto); + + User user = userRepository.findById(itemRequestCreateDto.getUserId()).orElseThrow( + () -> new NotFoundException("User not found with id: " + itemRequestCreateDto.getUserId()) + ); + + ItemRequest itemRequest = new ItemRequest(); + itemRequest.setRequestor(user); + itemRequest.setDescription(itemRequestCreateDto.getDescription()); + itemRequest.setCreated(LocalDateTime.now()); + + itemRequestRepository.save(itemRequest); + + return ItemRequestMapper.toItemRequestResponseDto(itemRequest, null); + } + + @Override + public List getListByUser(final Long userId) { + log.info("Get itemRequests by user: {}", userId); + + User user = userRepository.findById(userId).orElseThrow( + () -> new NotFoundException("User not found with id: " + userId) + ); + + return itemRequestRepository.findAllByRequestor_IdOrderByCreatedDesc(user.getId()) + .stream() + .map(itemRequest -> ItemRequestMapper.toItemRequestResponseDto(itemRequest, null)) + .toList(); + } + + @Override + public List getList(final Long userId) { + log.info("Get itemRequests with user: {}", userId); + + return ( + userId == null + ? itemRequestRepository.findAll(Sort.by(Sort.Direction.DESC, "created")) + : itemRequestRepository.findAllByRequestor_IdIsNotOrderByCreatedDesc(userId) + ).stream() + .map(itemRequest -> ItemRequestMapper.toItemRequestResponseDto(itemRequest, null)) + .toList(); + } + + @Override + public ItemRequestResponseDto getById(final Long id) { + log.info("Get itemRequest by id: {}", id); + + ItemRequest itemRequest = itemRequestRepository.findById(id).orElseThrow( + () -> new NotFoundException("Item request with id: " + id) + ); + + return ItemRequestMapper.toItemRequestResponseDto( + itemRequest, + itemRepository.findAllByRequest_IdOrderByIdDesc(itemRequest.getId()) + ); + } + +} diff --git a/server/src/main/java/ru/practicum/shareit/request/contracts/ItemRequestRepositoryInterface.java b/server/src/main/java/ru/practicum/shareit/request/contracts/ItemRequestRepositoryInterface.java new file mode 100644 index 0000000..afcc920 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/contracts/ItemRequestRepositoryInterface.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.request.contracts; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.List; + +public interface ItemRequestRepositoryInterface extends JpaRepository { + + List findAllByRequestor_IdOrderByCreatedDesc(Long id); + + List findAllByRequestor_IdIsNotOrderByCreatedDesc(Long id); + +} diff --git a/server/src/main/java/ru/practicum/shareit/request/contracts/ItemRequestServiceInterface.java b/server/src/main/java/ru/practicum/shareit/request/contracts/ItemRequestServiceInterface.java new file mode 100644 index 0000000..3492cd6 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/contracts/ItemRequestServiceInterface.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.request.contracts; + +import ru.practicum.shareit.request.dto.ItemRequestCreateDto; +import ru.practicum.shareit.request.dto.ItemRequestResponseDto; + +import java.util.List; + +public interface ItemRequestServiceInterface { + + ItemRequestResponseDto create(ItemRequestCreateDto itemRequestCreateDto); + + List getListByUser(Long userId); + + List getList(Long userId); + + ItemRequestResponseDto getById(Long id); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestCreateDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestCreateDto.java new file mode 100644 index 0000000..e353ac5 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestCreateDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.request.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ItemRequestCreateDto { + + private String description; + + private Long userId; +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestResponseDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestResponseDto.java new file mode 100644 index 0000000..d7de2d6 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestResponseDto.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.request.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ItemRequestResponseDto { + + private Long id; + + private String description; + + private LocalDateTime created; + + private List items; + + public record ItemDto(Long id, String name) { + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java new file mode 100644 index 0000000..cd45e71 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/mapper/ItemRequestMapper.java @@ -0,0 +1,34 @@ +package ru.practicum.shareit.request.mapper; + +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dto.ItemRequestResponseDto; +import ru.practicum.shareit.request.model.ItemRequest; + +import java.util.ArrayList; +import java.util.List; + +public class ItemRequestMapper { + + private ItemRequestMapper() { + } + + public static ItemRequestResponseDto toItemRequestResponseDto(ItemRequest itemRequest, List items) { + List itemDtos = new ArrayList<>(); + + if (items != null) { + for (Item item : items) { + itemDtos.add( + new ItemRequestResponseDto.ItemDto(item.getId(), item.getName()) + ); + } + } + + return new ItemRequestResponseDto( + itemRequest.getId(), + itemRequest.getDescription(), + itemRequest.getCreated(), + itemDtos + ); + } + +} diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java similarity index 97% rename from src/main/java/ru/practicum/shareit/request/model/ItemRequest.java rename to server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java index 8bc1f22..5f320df 100644 --- a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java +++ b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -26,7 +26,7 @@ public class ItemRequest { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; + private Long id; private String description; diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/server/src/main/java/ru/practicum/shareit/user/UserController.java similarity index 90% rename from src/main/java/ru/practicum/shareit/user/UserController.java rename to server/src/main/java/ru/practicum/shareit/user/UserController.java index 9ddb2d2..17da548 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.user; -import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -23,13 +22,13 @@ public class UserController { private final UserServiceInterface userService; @PostMapping - public UserDto create(final @Valid @RequestBody UserCreateDto dto) { + public UserDto create(final @RequestBody UserCreateDto dto) { return userService.create(dto); } @PatchMapping("/{userId}") public UserDto update( - final @Valid @RequestBody UserUpdateDto dto, + final @RequestBody UserUpdateDto dto, final @PathVariable Long userId ) { dto.setId(userId); diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/UserMapper.java diff --git a/src/main/java/ru/practicum/shareit/user/UserService.java b/server/src/main/java/ru/practicum/shareit/user/UserService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/UserService.java diff --git a/src/main/java/ru/practicum/shareit/user/contracts/UserRepositoryInterface.java b/server/src/main/java/ru/practicum/shareit/user/contracts/UserRepositoryInterface.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/contracts/UserRepositoryInterface.java rename to server/src/main/java/ru/practicum/shareit/user/contracts/UserRepositoryInterface.java diff --git a/src/main/java/ru/practicum/shareit/user/contracts/UserServiceInterface.java b/server/src/main/java/ru/practicum/shareit/user/contracts/UserServiceInterface.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/contracts/UserServiceInterface.java rename to server/src/main/java/ru/practicum/shareit/user/contracts/UserServiceInterface.java diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserCreateDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserCreateDto.java new file mode 100644 index 0000000..8f1e459 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserCreateDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class UserCreateDto { + + private String name; + + private String email; + +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/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 server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDto.java new file mode 100644 index 0000000..3b47cff --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserUpdateDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.user.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class UserUpdateDto { + + private Long id; + + private String name; + + private String email; + +} diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/server/src/main/java/ru/practicum/shareit/user/model/User.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/model/User.java rename to server/src/main/java/ru/practicum/shareit/user/model/User.java diff --git a/src/main/resources/application.properties b/server/src/main/resources/application.properties similarity index 60% rename from src/main/resources/application.properties rename to server/src/main/resources/application.properties index 9ba84fe..c1d43cb 100644 --- a/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,14 +1,18 @@ +server.port=9090 + spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.format_sql=true #spring.jpa.properties.hibernate.show_sql=true spring.sql.init.mode=always -logging.level.org.springframework.orm.jpa=INFO -logging.level.org.springframework.transaction=INFO -logging.level.org.springframework.transaction.interceptor=TRACE -logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG - +#--- spring.datasource.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/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 100% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/server/src/test/java/ru/practicum/shareit/ShareItTests.java similarity index 100% rename from src/test/java/ru/practicum/shareit/ShareItTests.java rename to server/src/test/java/ru/practicum/shareit/ShareItTests.java diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTests.java b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTests.java new file mode 100644 index 0000000..fb93f8e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTests.java @@ -0,0 +1,146 @@ +package ru.practicum.shareit.booking; + +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.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.BookingStatus; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Random; + +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.patch; +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 = BookingController.class) +class BookingControllerTests { + + @Autowired + ObjectMapper mapper; + + @MockBean + BookingService bookingService; + + @Autowired + private MockMvc mockMvc; + + @Test + void createBooking() throws Exception { + BookingDto bookingDto = makeBookingDto(); + + when(bookingService.create(Mockito.any(BookingCreateDto.class))) + .thenReturn(bookingDto); + + mockMvc.perform( + post("/bookings") + .content(mapper.writeValueAsString(new BookingCreateDto( + bookingDto.getStart(), + bookingDto.getEnd(), + bookingDto.getBooker().id(), + bookingDto.getItem().id() + ))) + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.item.name").value(bookingDto.getItem().name())); + } + + @Test + void approveBooking() throws Exception { + BookingDto bookingDto = makeBookingDto(); + + when(bookingService.approve(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyBoolean())) + .thenReturn(bookingDto); + + mockMvc.perform( + patch("/bookings/{id}?approved={approve}", bookingDto.getId(), true) + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.item.name").value(bookingDto.getItem().name())); + } + + @Test + void getById() throws Exception { + BookingDto bookingDto = makeBookingDto(); + + when(bookingService.getById(Mockito.anyLong(), Mockito.anyLong())) + .thenReturn(bookingDto); + + mockMvc.perform( + get("/bookings/{id}", bookingDto.getId()) + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.item.name").value(bookingDto.getItem().name())); + } + + @Test + void getByBookerAndState() throws Exception { + BookingDto bookingDto = makeBookingDto(); + + when(bookingService.getByBookerAndState(Mockito.anyLong(), Mockito.any())) + .thenReturn(List.of(bookingDto)); + + mockMvc.perform( + get("/bookings") + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].item.name").value(bookingDto.getItem().name())); + } + + @Test + void getByOwnerAndState() throws Exception { + BookingDto bookingDto = makeBookingDto(); + + when(bookingService.getByOwnerAndState(Mockito.anyLong(), Mockito.any())) + .thenReturn(List.of(bookingDto)); + + mockMvc.perform( + get("/bookings/owner") + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].item.name").value(bookingDto.getItem().name())); + } + + private BookingDto makeBookingDto() { + return new BookingDto( + new Random().nextLong(), + new BookingDto.ItemDto(null, "item name"), + new BookingDto.BookerDto(null, "user name"), + LocalDateTime.now(), + LocalDateTime.now().plusDays(2L), + BookingStatus.APPROVED + ); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingDtoTests.java b/server/src/test/java/ru/practicum/shareit/booking/BookingDtoTests.java new file mode 100644 index 0000000..75267d7 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingDtoTests.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit.booking; + +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.dto.BookingDto; +import ru.practicum.shareit.booking.model.BookingStatus; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class BookingDtoTests { + + private final JacksonTester json; + + @Test + void bookingDto() throws Exception { + BookingDto bookingDto = new BookingDto( + null, + new BookingDto.ItemDto(null, "item name"), + new BookingDto.BookerDto(null, "user name"), + LocalDateTime.now(), + LocalDateTime.now().plusDays(2L), + BookingStatus.APPROVED + ); + + JsonContent result = json.write(bookingDto); + + assertThat(result).extractingJsonPathStringValue("$.item.name").isEqualTo(bookingDto.getItem().name()); + assertThat(result).extractingJsonPathStringValue("$.booker.name").isEqualTo(bookingDto.getBooker().name()); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingServiceTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceTest.java new file mode 100644 index 0000000..840d894 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceTest.java @@ -0,0 +1,318 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import ru.practicum.shareit.booking.contracts.BookingRepositoryInterface; +import ru.practicum.shareit.booking.dto.BookingCreateDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingState; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.exception.InvalidBookingStatusException; +import ru.practicum.shareit.exception.InvalidOwnerException; +import ru.practicum.shareit.exception.ItemUnavailableException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.contracts.ItemRepositoryInterface; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.contracts.UserRepositoryInterface; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.Random; + +@SpringBootTest +@ExtendWith(MockitoExtension.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class BookingServiceTest { + + @Autowired + BookingRepositoryInterface bookingRepository; + + @Autowired + ItemRepositoryInterface itemRepository; + + @Autowired + UserRepositoryInterface userRepository; + + @Autowired + BookingService bookingService; + + @Test + void create() { + User owner = createUser(); + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(owner, item); + + BookingDto bookingRes = bookingService.create(bookingCreateDto); + + Optional booking = bookingRepository.findById(bookingRes.getId()); + + Assertions.assertTrue(booking.isPresent()); + } + + @Test + void createWithInvalidItemReturnThrow() { + Random random = new Random(); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> bookingService.create(makeBookingCreateDto( + new User(random.nextLong(), null, null), + new Item(random.nextLong(), null, null, true, null, null) + )) + ); + } + + @Test + void createWithInvalidUserReturnThrow() { + User owner = createUser(); + User user = new User(new Random().nextLong(), null, null); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> bookingService.create(makeBookingCreateDto( + user, + createItem(owner) + )) + ); + } + + @Test + void createWithUnavailableItemReturnThrow() { + User owner = createUser(); + + Item item = createItem(owner); + item.setAvailable(false); + itemRepository.save(item); + + Assertions.assertThrowsExactly( + ItemUnavailableException.class, + () -> bookingService.create(makeBookingCreateDto( + owner, + item + )) + ); + } + + @Test + void approve() { + User owner = createUser(); + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(owner, item); + + BookingDto bookingRes = bookingService.create(bookingCreateDto); + + BookingDto booking = bookingService.approve( + bookingRes.getId(), + owner.getId(), + true + ); + + Assertions.assertNotNull(booking); + Assertions.assertEquals(BookingStatus.APPROVED, booking.getStatus()); + } + + @Test + void approveWithInvalidIdReturnThrow() { + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> bookingService.approve(new Random().nextLong(), null, true) + ); + } + + @Test + void approveWithInvalidStatusReturnThrow() { + User owner = createUser(); + Item item = createItem(owner); + + Booking booking = bookingRepository.save(new Booking( + null, + item, + owner, + LocalDateTime.now(), + LocalDateTime.now().plusDays(1), + BookingStatus.APPROVED + )); + + Assertions.assertThrowsExactly( + InvalidBookingStatusException.class, + () -> bookingService.approve(booking.getId(), owner.getId(), true) + ); + } + + @Test + void approveWithInvalidOwnerReturnThrow() { + User owner = createUser(); + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(owner, item); + BookingDto bookingRes = bookingService.create(bookingCreateDto); + User invalidOwner = createUser(); + + Assertions.assertThrowsExactly( + InvalidOwnerException.class, + () -> bookingService.approve(bookingRes.getId(), invalidOwner.getId(), true) + ); + } + + @Test + void getById() { + User owner = createUser(); + User booker = createUser(); + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(booker, item); + + BookingDto bookingRes = bookingService.create(bookingCreateDto); + BookingDto booking = bookingService.getById(bookingRes.getId(), owner.getId()); + + Assertions.assertEquals(bookingRes.getId(), booking.getId()); + } + + @Test + void getByIdWithInvalidIdReturnThrow() { + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> bookingService.getById(new Random().nextLong(), null) + ); + } + + @Test + void getByIdAndOwner() { + User owner = createUser(); + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(owner, item); + + BookingDto bookingRes = bookingService.create(bookingCreateDto); + BookingDto booking = bookingService.getById(bookingRes.getId(), owner.getId()); + + Assertions.assertEquals(bookingRes.getId(), booking.getId()); + } + + @Test + void getByIdAndWrongBooker() { + User owner = createUser(); + User wrongBooker = createUser(); + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(owner, item); + + BookingDto bookingRes = bookingService.create(bookingCreateDto); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> bookingService.getById(bookingRes.getId(), wrongBooker.getId()) + ); + } + + @Test + void findItemsByOwner() { + User owner = createUser(); + int ownerItemsCount = new Random().nextInt(10); + + for (int i = 1; i <= ownerItemsCount; i++) { + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(owner, item); + bookingService.create(bookingCreateDto); + } + + Assertions.assertEquals( + ownerItemsCount, + bookingService.getByOwnerAndState(owner.getId(), BookingState.ALL).size() + ); + Assertions.assertEquals( + ownerItemsCount, + bookingService.getByOwnerAndState(owner.getId(), BookingState.CURRENT).size() + ); + Assertions.assertEquals( + 0, + bookingService.getByOwnerAndState(owner.getId(), BookingState.REJECTED).size() + ); + Assertions.assertEquals( + ownerItemsCount, + bookingService.getByOwnerAndState(owner.getId(), BookingState.WAITING).size() + ); + Assertions.assertEquals( + 0, + bookingService.getByOwnerAndState(owner.getId(), BookingState.PAST).size() + ); + Assertions.assertEquals( + 0, + bookingService.getByOwnerAndState(owner.getId(), BookingState.FUTURE).size() + ); + } + + @Test + void findItemsByBooker() { + User owner = createUser(); + User booker = createUser(); + int ownerItemsCount = new Random().nextInt(10); + + for (int i = 1; i <= ownerItemsCount; i++) { + Item item = createItem(owner); + BookingCreateDto bookingCreateDto = makeBookingCreateDto(booker, item); + bookingService.create(bookingCreateDto); + } + + Assertions.assertEquals( + ownerItemsCount, + bookingService.getByBookerAndState(booker.getId(), BookingState.ALL).size() + ); + Assertions.assertEquals( + ownerItemsCount, + bookingService.getByBookerAndState(booker.getId(), BookingState.CURRENT).size() + ); + Assertions.assertEquals( + 0, + bookingService.getByBookerAndState(booker.getId(), BookingState.REJECTED).size() + ); + Assertions.assertEquals( + ownerItemsCount, + bookingService.getByBookerAndState(booker.getId(), BookingState.WAITING).size() + ); + Assertions.assertEquals( + 0, + bookingService.getByBookerAndState(booker.getId(), BookingState.PAST).size() + ); + Assertions.assertEquals( + 0, + bookingService.getByBookerAndState(booker.getId(), BookingState.FUTURE).size() + ); + } + + private User createUser() { + long id = new Random().nextLong(); + + return userRepository.save(new User( + null, + "user name #" + id, + "user" + id + "@yandex.net" + )); + } + + private Item createItem(User owner) { + long id = new Random().nextLong(); + + return itemRepository.save(new Item( + null, + "name #" + id, + "description " + id, + true, + owner, + null + )); + } + + private BookingCreateDto makeBookingCreateDto(User booker, Item item) { + return new BookingCreateDto( + LocalDateTime.now(), + LocalDateTime.now().plusDays(2L), + item.getId(), + booker.getId() + ); + } + +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/CommentDtoTests.java b/server/src/test/java/ru/practicum/shareit/item/CommentDtoTests.java new file mode 100644 index 0000000..363785e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/CommentDtoTests.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.item; + +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.item.dto.CommentDto; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class CommentDtoTests { + + private final JacksonTester json; + + @Test + void commentDto() throws Exception { + LocalDateTime dateTime = LocalDateTime.now(); + + CommentDto commentDto = new CommentDto( + null, + "text", + "authorName", + dateTime + ); + + JsonContent result = json.write(commentDto); + + assertThat(result).extractingJsonPathStringValue("$.text").isEqualTo(commentDto.getText()); + assertThat(result).extractingJsonPathStringValue("$.authorName").isEqualTo(commentDto.getAuthorName()); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTests.java b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTests.java new file mode 100644 index 0000000..0ae6566 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTests.java @@ -0,0 +1,204 @@ +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.CommentCreateDto; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemCreateDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemInfoDto; +import ru.practicum.shareit.item.dto.ItemUpdateDto; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Random; + +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.patch; +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 = ItemController.class) +class ItemControllerTests { + + @Autowired + ObjectMapper mapper; + + @MockBean + ItemService itemService; + + @Autowired + private MockMvc mockMvc; + + @Test + void createItem() throws Exception { + ItemDto itemDto = makeItemDto(); + + when(itemService.create(Mockito.any(ItemCreateDto.class), Mockito.anyLong())) + .thenReturn(itemDto); + + mockMvc.perform( + post("/items") + .content(mapper.writeValueAsString(new ItemCreateDto( + itemDto.getName(), + itemDto.getDescription(), + itemDto.getAvailable(), + null + ))) + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(itemDto.getName())) + .andExpect(jsonPath("$.description").value(itemDto.getDescription())) + .andExpect(jsonPath("$.available").value(itemDto.getAvailable())); + } + + @Test + void updateItem() throws Exception { + ItemDto itemDto = makeItemDto(); + + when(itemService.update(Mockito.any(ItemUpdateDto.class), Mockito.anyLong())) + .thenReturn(itemDto); + + mockMvc.perform( + patch("/items/{id}", itemDto.getId()) + .content(mapper.writeValueAsString(itemDto)) + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(itemDto.getName())) + .andExpect(jsonPath("$.description").value(itemDto.getDescription())) + .andExpect(jsonPath("$.available").value(itemDto.getAvailable())); + } + + @Test + void getItem() throws Exception { + ItemInfoDto itemInfoDto = makeItemInfoDto(); + + when(itemService.findItemById(Mockito.anyLong(), Mockito.anyLong())) + .thenReturn(itemInfoDto); + + mockMvc.perform( + get("/items/{id}", itemInfoDto.getId()) + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(itemInfoDto.getName())) + .andExpect(jsonPath("$.description").value(itemInfoDto.getDescription())) + .andExpect(jsonPath("$.available").value(itemInfoDto.getAvailable())); + } + + @Test + void getItemsByOwner() throws Exception { + ItemInfoDto itemInfoDto = makeItemInfoDto(); + + when(itemService.findItemsByOwner(Mockito.anyLong())) + .thenReturn(List.of(itemInfoDto)); + + mockMvc.perform( + get("/items") + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value(itemInfoDto.getName())) + .andExpect(jsonPath("$[0].description").value(itemInfoDto.getDescription())) + .andExpect(jsonPath("$[0].available").value(itemInfoDto.getAvailable())); + } + + @Test + void search() throws Exception { + ItemDto itemDto = makeItemDto(); + String text = "search text"; + + when(itemService.findItemsByText(text)) + .thenReturn(List.of(itemDto)); + + mockMvc.perform( + get("/items/search") + .header("X-Sharer-User-Id", "1") + .param("text", text) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value(itemDto.getName())) + .andExpect(jsonPath("$[0].description").value(itemDto.getDescription())) + .andExpect(jsonPath("$[0].available").value(itemDto.getAvailable())); + } + + @Test + void addComment() throws Exception { + ItemDto itemDto = makeItemDto(); + CommentDto commentDto = new CommentDto( + new Random().nextLong(), + "comment text", + "author name", + LocalDateTime.now() + ); + + when(itemService.addComment(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(CommentCreateDto.class))) + .thenReturn(commentDto); + + mockMvc.perform( + post("/items/{id}/comment", itemDto.getId()) + .content(mapper.writeValueAsString(new CommentCreateDto( + commentDto.getText() + ))) + .header("X-Sharer-User-Id", "1") + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.text").value(commentDto.getText())) + .andExpect(jsonPath("$.authorName").value(commentDto.getAuthorName())); + } + + private ItemDto makeItemDto() { + Long id = new Random().nextLong(); + + return new ItemDto( + id, + "item name #" + id, + "item description #" + id, + true + ); + } + + private ItemInfoDto makeItemInfoDto() { + Long id = new Random().nextLong(); + + return new ItemInfoDto( + id, + "item name #" + id, + "item description #" + id, + true, + null, + null, + null + ); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemDtoTests.java b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTests.java new file mode 100644 index 0000000..42d1b76 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemDtoTests.java @@ -0,0 +1,35 @@ +package ru.practicum.shareit.item; + +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.item.dto.ItemDto; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class ItemDtoTests { + + private final JacksonTester json; + + @Test + void itemDto() throws Exception { + ItemDto itemDto = new ItemDto( + null, + "name", + "desc", + false + ); + + JsonContent result = json.write(itemDto); + + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo(itemDto.getName()); + assertThat(result).extractingJsonPathStringValue("$.description").isEqualTo(itemDto.getDescription()); + assertThat(result).extractingJsonPathBooleanValue("$.available").isEqualTo(itemDto.getAvailable()); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemRepositoryTests.java b/server/src/test/java/ru/practicum/shareit/item/ItemRepositoryTests.java new file mode 100644 index 0000000..1c296c9 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemRepositoryTests.java @@ -0,0 +1,113 @@ +package ru.practicum.shareit.item; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import ru.practicum.shareit.item.contracts.ItemRepositoryInterface; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +@DataJpaTest() +class ItemRepositoryTests { + + @Autowired + ItemRepositoryInterface repository; + + @Autowired + TestEntityManager em; + + @Test + void findAllByOwnerId() { + int rowsCount = 0; + int userCount = 2; + + List userList = new ArrayList<>(userCount); + + for (int j = 1; j <= userCount; j++) { + User user = new User(null, "username" + j, "user" + j); + em.persist(user); + userList.add(user); + + rowsCount = new Random().nextInt(1, 10); + + for (int i = 1; i <= rowsCount; i++) { + Item item = new Item( + null, + "Name" + i, + "Description" + i, + true, + user, + null + ); + em.persist(item); + } + } + + em.flush(); + + Assertions.assertEquals( + rowsCount, + repository.findAllByOwnerId( + userList.getLast().getId() + ).size() + ); + } + + @Test + void findAllByText() { + + User user = new User(null, "username", "user"); + em.persist(user); + + Item item = new Item( + null, + "Name", + "Description", + true, + user, + null + ); + em.persist(item); + + em.flush(); + + Assertions.assertEquals(1, repository.findAllByText("description").size()); + Assertions.assertEquals(1, repository.findAllByText("name").size()); + Assertions.assertEquals(0, repository.findAllByText("some text").size()); + } + + @Test + void findAllByRequest_IdOrderByIdDesc() { + + User user = new User(null, "username", "user"); + em.persist(user); + + ItemRequest itemRequest = new ItemRequest(null, "Description", user, LocalDateTime.now()); + em.persist(itemRequest); + + Item item = new Item( + null, + "Name", + "Description", + true, + user, + itemRequest + ); + em.persist(item); + + em.flush(); + + Assertions.assertEquals( + 1, + repository.findAllByRequest_IdOrderByIdDesc(itemRequest.getId()).size() + ); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemServiceTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemServiceTest.java new file mode 100644 index 0000000..3db7391 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemServiceTest.java @@ -0,0 +1,461 @@ +package ru.practicum.shareit.item; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Sort; +import org.springframework.test.annotation.DirtiesContext; +import ru.practicum.shareit.booking.contracts.BookingRepositoryInterface; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.exception.EmptyIdException; +import ru.practicum.shareit.exception.InvalidOwnerException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.UserDoesNotHaveBookedItem; +import ru.practicum.shareit.item.contracts.CommentRepositoryInterface; +import ru.practicum.shareit.item.contracts.ItemRepositoryInterface; +import ru.practicum.shareit.item.dto.CommentCreateDto; +import ru.practicum.shareit.item.dto.ItemCreateDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemInfoDto; +import ru.practicum.shareit.item.dto.ItemUpdateDto; +import ru.practicum.shareit.item.model.Comment; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.contracts.ItemRequestRepositoryInterface; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.contracts.UserRepositoryInterface; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.Random; + +@SpringBootTest +@ExtendWith(MockitoExtension.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class ItemServiceTest { + + @MockBean + BookingRepositoryInterface bookingRepository; + + @Autowired + ItemRepositoryInterface itemRepository; + + @Autowired + UserRepositoryInterface userRepository; + + @Autowired + CommentRepositoryInterface commentRepository; + + @Autowired + ItemService itemService; + + @Autowired + ItemRequestRepositoryInterface itemRequestRepository; + + @Test + void create() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + Optional item = itemRepository.findById(itemRes.getId()); + + Assertions.assertTrue(item.isPresent()); + } + + @Test + void createWithRequest() { + User owner = createUser(); + ItemRequest itemRequest = itemRequestRepository.save(new ItemRequest( + null, + "Request description", + owner, + LocalDateTime.now() + )); + + ItemCreateDto itemCreateDto = makeItemCreateDto(); + itemCreateDto.setRequestId(itemRequest.getId()); + + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + Optional item = itemRepository.findById(itemRes.getId()); + + Assertions.assertTrue(item.isPresent()); + } + + @Test + void createWithWrongUserReturnThrow() { + ItemCreateDto itemCreateDto = makeItemCreateDto(); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.create(itemCreateDto, new Random().nextLong()) + ); + } + + @Test + void createWithWrongRequestReturnThrow() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + itemCreateDto.setRequestId(new Random().nextLong()); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.create(itemCreateDto, owner.getId()) + ); + } + + @Test + void update() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + String itemNewName = "item new name"; + + itemService.update( + new ItemUpdateDto( + itemRes.getId(), + itemNewName, + itemRes.getDescription(), + itemRes.getAvailable() + ), + owner.getId() + ); + + Optional item = itemRepository.findById(itemRes.getId()); + + Assertions.assertTrue(item.isPresent()); + Assertions.assertEquals(itemNewName, item.get().getName()); + } + + @Test + void updateWithWrongIdReturnThrows() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.update( + new ItemUpdateDto( + new Random().nextLong(), + itemCreateDto.getName(), + itemCreateDto.getDescription(), + itemCreateDto.getAvailable() + ), + owner.getId() + ) + ); + } + + @Test + void updateWithWrongOwnerReturnThrow() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + User wrongOwner = createUser(); + + Assertions.assertThrowsExactly( + InvalidOwnerException.class, + () -> itemService.update( + new ItemUpdateDto( + itemRes.getId(), + itemRes.getName(), + itemRes.getDescription(), + itemRes.getAvailable() + ), + wrongOwner.getId() + ) + ); + } + + @Test + void updateWithWrongUserReturnThrow() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.update( + new ItemUpdateDto( + itemRes.getId(), + itemRes.getName(), + itemRes.getDescription(), + itemRes.getAvailable() + ), + new Random().nextLong() + ) + ); + } + + @Test + void updateWithInvalidItemIdReturnThrow() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + Assertions.assertThrowsExactly( + EmptyIdException.class, + () -> itemService.update( + new ItemUpdateDto( + null, + itemRes.getName(), + itemRes.getDescription(), + itemRes.getAvailable() + ), + new Random().nextLong() + ) + ); + } + + @Test + void findItemById() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + User user = createUser(); + ItemInfoDto item = itemService.findItemById(itemRes.getId(), user.getId()); + + Assertions.assertNotNull(item); + Assertions.assertEquals(item.getId(), itemRes.getId()); + } + + @Test + void findItemByIdWithWrongIdReturnThrow() { + Random random = new Random(); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.findItemById(random.nextLong(), random.nextLong()) + ); + } + + @Test + void findItemByIdByOwner() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + ItemInfoDto item = itemService.findItemById(itemRes.getId(), owner.getId()); + + Assertions.assertNotNull(item); + Assertions.assertEquals(item.getId(), itemRes.getId()); + } + + @Test + void findItemsByOwner() { + User owner = createUser(); + int ownerItemsCount = new Random().nextInt(2, 10); + ItemDto itemDto = null; + + for (int i = 1; i <= ownerItemsCount; i++) { + ItemCreateDto itemCreateDto = makeItemCreateDto(); + itemDto = itemService.create(itemCreateDto, owner.getId()); + } + + User user = createUser(); + int userItemsCount = new Random().nextInt(2,10); + + for (int i = 1; i <= userItemsCount; i++) { + ItemCreateDto itemCreateDto = makeItemCreateDto(); + itemService.create(itemCreateDto, user.getId()); + } + + Random random = new Random(); + Item item = new Item( + itemDto.getId(), + itemDto.getName(), + itemDto.getDescription(), + itemDto.getAvailable(), + owner, + null + ); + + Mockito.when(bookingRepository.findAllByItem_IdInAndStatus( + Mockito.anyList(), + Mockito.any(), + Mockito.any() + )).thenReturn( + List.of( + new Booking( + random.nextLong(), + item, + user, + LocalDateTime.now().minusDays(4), + LocalDateTime.now().minusDays(3), + BookingStatus.APPROVED + ), + new Booking( + random.nextLong(), + item, + user, + LocalDateTime.now().plusDays(1), + LocalDateTime.now().plusDays(2), + BookingStatus.APPROVED + ) + ) + ); + + commentRepository.save(new Comment( + null, + "comment text", + item, + user, + LocalDateTime.now() + )); + + List items = itemService.findItemsByOwner(owner.getId()); + + Assertions.assertEquals(ownerItemsCount, items.size()); + } + + @Test + void findItemsByOwnerWithWrongIdReturnThrow() { + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.findItemsByOwner(new Random().nextLong()) + ); + } + + @Test + void findItemsByText() { + String searchText = "text for search test"; + + ItemCreateDto itemCreateDto = makeItemCreateDto(); + itemCreateDto.setDescription(searchText); + User owner = createUser(); + + itemService.create(itemCreateDto, owner.getId()); + + List res = itemService.findItemsByText(searchText); + + Assertions.assertFalse(res.isEmpty()); + Assertions.assertEquals(searchText, res.getFirst().getDescription()); + + } + + @Test + void findItemsByTextWithEmptyString() { + + ItemCreateDto itemCreateDto = makeItemCreateDto(); + User owner = createUser(); + + itemService.create(itemCreateDto, owner.getId()); + + List res = itemService.findItemsByText(""); + + Assertions.assertTrue(res.isEmpty()); + } + + @Test + void addComment() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + User user = createUser(); + String commentText = "test comment"; + + Mockito.when( + bookingRepository.existsByBookerIdAndItemIdAndStatusEqualsAndEndIsBefore(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any()) + ).thenReturn(true); + + itemService.addComment( + itemRes.getId(), + user.getId(), + new CommentCreateDto(commentText) + ); + + List comments = commentRepository.findAllByItem_Id(itemRes.getId(), Sort.by(Sort.Direction.DESC, "created")); + + Assertions.assertFalse(comments.isEmpty()); + Assertions.assertEquals(commentText, comments.getFirst().getText()); + } + + @Test + void addCommentWithWrongIdReturnThrow() { + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.addComment( + new Random().nextLong(), + null, + new CommentCreateDto("text") + ) + ); + } + + @Test + void addCommentWithWrongUserReturnThrow() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemService.addComment( + itemRes.getId(), + new Random().nextLong(), + new CommentCreateDto("text") + ) + ); + } + + @Test + void addCommentWithInvalidBookerIdReturnThrow() { + User owner = createUser(); + ItemCreateDto itemCreateDto = makeItemCreateDto(); + ItemDto itemRes = itemService.create(itemCreateDto, owner.getId()); + + Mockito.when( + bookingRepository.existsByBookerIdAndItemIdAndStatusEqualsAndEndIsBefore( + Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any() + ) + ).thenReturn(false); + + User user = createUser(); + + Assertions.assertThrowsExactly( + UserDoesNotHaveBookedItem.class, + () -> itemService.addComment( + itemRes.getId(), + user.getId(), + new CommentCreateDto("comment text") + ) + ); + } + + private User createUser() { + Random random = new Random(); + + return userRepository.save(new User( + null, + "user name #" + random.nextInt(), + "user" + random.nextInt() + "@yandex.net" + )); + } + + private ItemCreateDto makeItemCreateDto() { + return new ItemCreateDto( + "Item name", + "item description", + true, + null + ); + } + +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTests.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTests.java new file mode 100644 index 0000000..8119bfc --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTests.java @@ -0,0 +1,140 @@ +package ru.practicum.shareit.request; + +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.request.dto.ItemRequestCreateDto; +import ru.practicum.shareit.request.dto.ItemRequestResponseDto; +import ru.practicum.shareit.user.model.User; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +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 ItemRequestControllerTests { + + @Autowired + ObjectMapper mapper; + + @MockBean + ItemRequestService itemRequestService; + + @Autowired + private MockMvc mockMvc; + + @Test + void create() throws Exception { + ItemRequestResponseDto itemRequestResponseDto = makeItemRequestDto(); + User user = makeUser(); + + when(itemRequestService.create(Mockito.any(ItemRequestCreateDto.class))) + .thenReturn(itemRequestResponseDto); + + mockMvc.perform( + post("/requests") + .content(mapper.writeValueAsString(new ItemRequestCreateDto( + itemRequestResponseDto.getDescription(), + user.getId() + ))) + .header("X-Sharer-User-Id", user.getId().toString()) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description").value(itemRequestResponseDto.getDescription())); + } + + @Test + void getListByUser() throws Exception { + ItemRequestResponseDto itemRequestResponseDto = makeItemRequestDto(); + User user = makeUser(); + + when(itemRequestService.getListByUser(Mockito.anyLong())) + .thenReturn(List.of(itemRequestResponseDto)); + + mockMvc.perform( + get("/requests") + .header("X-Sharer-User-Id", user.getId().toString()) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].description").value(itemRequestResponseDto.getDescription())); + } + + @Test + void getList() throws Exception { + ItemRequestResponseDto itemRequestResponseDto = makeItemRequestDto(); + User user = makeUser(); + + when(itemRequestService.getList(Mockito.anyLong())) + .thenReturn(List.of(itemRequestResponseDto)); + + mockMvc.perform( + get("/requests/all") + .header("X-Sharer-User-Id", user.getId().toString()) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].description").value(itemRequestResponseDto.getDescription())); + } + + @Test + void getById() throws Exception { + ItemRequestResponseDto itemRequestResponseDto = makeItemRequestDto(); + User user = makeUser(); + + when(itemRequestService.getById(Mockito.anyLong())) + .thenReturn(itemRequestResponseDto); + + mockMvc.perform( + get("/requests/{id}", itemRequestResponseDto.getId()) + .header("X-Sharer-User-Id", user.getId().toString()) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description").value(itemRequestResponseDto.getDescription())); + } + + private ItemRequestResponseDto makeItemRequestDto() { + Long id = new Random().nextLong(); + + return new ItemRequestResponseDto( + id, + "description #" + id, + LocalDateTime.now(), + Collections.emptyList() + ); + } + + private User makeUser() { + Long id = new Random().nextLong(); + + return new User( + id, + "user name #" + id, + "user-" + id + "@yandex.net" + ); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryTests.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryTests.java new file mode 100644 index 0000000..0fabccc --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestRepositoryTests.java @@ -0,0 +1,95 @@ +package ru.practicum.shareit.request; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import ru.practicum.shareit.request.contracts.ItemRequestRepositoryInterface; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +@DataJpaTest() +class ItemRequestRepositoryTests { + + @Autowired + ItemRequestRepositoryInterface repository; + + @Autowired + TestEntityManager em; + + @Test + void findAllByRequestor_IdOrderByCreatedDesc() { + int rowsCount = 0; + int userCount = 2; + + List userList = new ArrayList<>(userCount); + + for (int j = 1; j <= userCount; j++) { + User user = new User(null, "username" + j, "user" + j); + em.persist(user); + userList.add(user); + + rowsCount = new Random().nextInt(1, 10); + + for (int i = 1; i <= rowsCount; i++) { + ItemRequest itemRequest = new ItemRequest( + null, + "description", + user, + LocalDateTime.now() + ); + em.persist(itemRequest); + } + } + + em.flush(); + + Assertions.assertEquals( + rowsCount, + repository.findAllByRequestor_IdOrderByCreatedDesc( + userList.getLast().getId() + ).size() + ); + } + + @Test + void findAllByRequestor_IdIsNotOrderByCreatedDesc() { + int rowsCount = 0; + int userCount = 2; + + List userList = new ArrayList<>(userCount); + + for (int j = 1; j <= userCount; j++) { + User user = new User(null, "username" + j, "user" + j); + em.persist(user); + userList.add(user); + + rowsCount = new Random().nextInt(1, 10); + + for (int i = 1; i <= rowsCount; i++) { + ItemRequest itemRequest = new ItemRequest( + null, + "description", + user, + LocalDateTime.now() + ); + em.persist(itemRequest); + } + } + + em.flush(); + + Assertions.assertEquals( + rowsCount, + repository.findAllByRequestor_IdIsNotOrderByCreatedDesc( + userList.getFirst().getId() + ).size() + ); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceTest.java new file mode 100644 index 0000000..0a904c7 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestServiceTest.java @@ -0,0 +1,181 @@ +package ru.practicum.shareit.request; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.annotation.DirtiesContext; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.contracts.ItemRepositoryInterface; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.contracts.ItemRequestRepositoryInterface; +import ru.practicum.shareit.request.dto.ItemRequestCreateDto; +import ru.practicum.shareit.request.dto.ItemRequestResponseDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.contracts.UserRepositoryInterface; +import ru.practicum.shareit.user.model.User; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Random; + +@SpringBootTest +@ExtendWith(MockitoExtension.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class ItemRequestServiceTest { + + @Autowired + ItemRequestRepositoryInterface itemRequestRepository; + + @MockBean + ItemRepositoryInterface itemRepository; + + @Autowired + UserRepositoryInterface userRepository; + + @Autowired + ItemRequestService itemRequestService; + + @BeforeEach + void setUp() { + userRepository.deleteAll(); + itemRepository.deleteAll(); + itemRequestRepository.deleteAll(); + } + + @Test + void create() { + User user = createUser(); + ItemRequestCreateDto itemRequestCreateDto = makeItemRequestCreateDto(user); + + ItemRequestResponseDto itemRequestResponseDto = itemRequestService.create(itemRequestCreateDto); + + Optional itemRequest = itemRequestRepository.findById(itemRequestResponseDto.getId()); + + Assertions.assertTrue(itemRequest.isPresent()); + } + + @Test + void createWithInvalidUserReturnThrow() { + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemRequestService.create(makeItemRequestCreateDto( + new User(new Random().nextLong(), "username", "email") + )) + ); + } + + @Test + void getById() { + User user = createUser(); + ItemRequestCreateDto itemRequestCreateDto = makeItemRequestCreateDto(user); + + ItemRequestResponseDto itemRequestResponseDto = itemRequestService.create(itemRequestCreateDto); + + Mockito.when( + itemRepository.findAllByRequest_IdOrderByIdDesc(Mockito.anyLong()) + ).thenReturn( + List.of( + new Item( + null, + "item name", + "item description", + true, + user, + null + ) + ) + ); + + ItemRequestResponseDto itemRequest = itemRequestService.getById(itemRequestResponseDto.getId()); + + Assertions.assertEquals(itemRequestResponseDto.getId(), itemRequest.getId()); + Assertions.assertFalse(itemRequest.getItems().isEmpty()); + } + + @Test + void getByIdWithInvalidIdReturnThrow() { + Assertions.assertThrows( + NotFoundException.class, () -> itemRequestService.getById(new Random().nextLong()) + ); + } + + @Test + void getListByUser() { + User user = createUser(); + int userRequestsCount = new Random().nextInt(10); + + for (int i = 1; i <= userRequestsCount; i++) { + ItemRequestCreateDto itemRequestCreateDto = makeItemRequestCreateDto(user); + itemRequestService.create(itemRequestCreateDto); + } + + List itemRequests = itemRequestService.getListByUser(user.getId()); + + Assertions.assertEquals(userRequestsCount, itemRequests.size()); + } + + @Test + void getListByUserWithInvalidUserReturnThrow() { + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> itemRequestService.getListByUser(new Random().nextLong()) + ); + } + + @Test + void getList() { + List users = new ArrayList<>(); + + for (int j = 0; j < 2; j++) { + User user = createUser(); + users.add(user); + + ItemRequestCreateDto itemRequestCreateDto = makeItemRequestCreateDto(user); + itemRequestService.create(itemRequestCreateDto); + } + + List itemRequests = itemRequestService.getList(users.getLast().getId()); + + Assertions.assertEquals(1, itemRequests.size()); + } + + @Test + void getListWithAllUsers() { + + for (int j = 0; j < 2; j++) { + User user = createUser(); + + ItemRequestCreateDto itemRequestCreateDto = makeItemRequestCreateDto(user); + itemRequestService.create(itemRequestCreateDto); + } + + List itemRequests = itemRequestService.getList(null); + + Assertions.assertEquals(2, itemRequests.size()); + } + + private User createUser() { + long id = new Random().nextLong(); + + return userRepository.save(new User( + null, + "user name #" + id, + "user" + id + "@yandex.net" + )); + } + + private ItemRequestCreateDto makeItemRequestCreateDto(User user) { + return new ItemRequestCreateDto( + "description", + user.getId() + ); + } + +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/RequestResponseDtoTests.java b/server/src/test/java/ru/practicum/shareit/request/RequestResponseDtoTests.java new file mode 100644 index 0000000..b9a78d2 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/RequestResponseDtoTests.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.request; + +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.request.dto.ItemRequestResponseDto; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class RequestResponseDtoTests { + + private final JacksonTester json; + + @Test + void itemRequestResponseDto() throws Exception { + ItemRequestResponseDto itemRequestResponseDto = new ItemRequestResponseDto( + null, + "description", + LocalDateTime.now(), + List.of( + new ItemRequestResponseDto.ItemDto(null, "item name") + ) + ); + + JsonContent result = json.write(itemRequestResponseDto); + + assertThat(result).extractingJsonPathStringValue("$.description").isEqualTo(itemRequestResponseDto.getDescription()); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/user/UserControllerTests.java b/server/src/test/java/ru/practicum/shareit/user/UserControllerTests.java new file mode 100644 index 0000000..6125b32 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserControllerTests.java @@ -0,0 +1,124 @@ +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.UserCreateDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.dto.UserUpdateDto; + +import java.nio.charset.StandardCharsets; +import java.util.Random; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +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 = UserController.class) +class UserControllerTests { + + @Autowired + ObjectMapper mapper; + + @MockBean + UserService userService; + + @Autowired + private MockMvc mockMvc; + + @Test + void createUser() throws Exception { + UserDto userDto = makeUser(); + + when(userService.create(Mockito.any(UserCreateDto.class))) + .thenReturn(userDto); + + mockMvc.perform( + post("/users") + .content(mapper.writeValueAsString(new UserCreateDto( + userDto.getName(), + userDto.getEmail() + ))) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(userDto.getName())) + .andExpect(jsonPath("$.email").value(userDto.getEmail())); + } + + @Test + void updateUser() throws Exception { + UserDto userDto = makeUser(); + + when(userService.update(Mockito.any(UserUpdateDto.class))) + .thenReturn(userDto); + + mockMvc.perform( + patch("/users/{id}", userDto.getId()) + .content(mapper.writeValueAsString(new UserUpdateDto( + userDto.getId(), + userDto.getName(), + userDto.getEmail() + ))) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(userDto.getName())) + .andExpect(jsonPath("$.email").value(userDto.getEmail())); + } + + @Test + void getUser() throws Exception { + UserDto userDto = makeUser(); + + when(userService.findById(Mockito.anyLong())) + .thenReturn(userDto); + + mockMvc.perform( + get("/users/{id}", userDto.getId()) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(userDto.getName())) + .andExpect(jsonPath("$.email").value(userDto.getEmail())); + } + + @Test + void deleteUser() throws Exception { + Mockito.doNothing().when(userService).deleteById(Mockito.anyLong()); + + mockMvc.perform( + delete("/users/{id}", new Random().nextLong()) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()); + } + + private UserDto makeUser() { + Long id = new Random().nextLong(); + + return new UserDto( + id, + "username #" + id, + "user-" + id + "@yandex.net" + ); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/user/UserDtoTests.java b/server/src/test/java/ru/practicum/shareit/user/UserDtoTests.java new file mode 100644 index 0000000..fd0295b --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserDtoTests.java @@ -0,0 +1,33 @@ +package ru.practicum.shareit.user; + +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.user.dto.UserDto; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@JsonTest +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class UserDtoTests { + + private final JacksonTester json; + + @Test + void userDto() throws Exception { + UserDto userDto = new UserDto( + null, + "username", + "user@yandex.net" + ); + + JsonContent result = json.write(userDto); + + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo(userDto.getName()); + assertThat(result).extractingJsonPathStringValue("$.email").isEqualTo(userDto.getEmail()); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/user/UserRepositoryTests.java b/server/src/test/java/ru/practicum/shareit/user/UserRepositoryTests.java new file mode 100644 index 0000000..845a01d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserRepositoryTests.java @@ -0,0 +1,32 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import ru.practicum.shareit.user.contracts.UserRepositoryInterface; +import ru.practicum.shareit.user.model.User; + +@DataJpaTest() +class UserRepositoryTests { + + @Autowired + UserRepositoryInterface repository; + + @Autowired + TestEntityManager em; + + @Test + void existsByEmail() { + String email = "user@somehost.net"; + User user = new User(null, "username", email); + + em.persist(user); + em.flush(); + + Assertions.assertTrue( + repository.existsByEmail(email) + ); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/UserServiceTest.java b/server/src/test/java/ru/practicum/shareit/user/UserServiceTest.java new file mode 100644 index 0000000..237d5a2 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserServiceTest.java @@ -0,0 +1,172 @@ +package ru.practicum.shareit.user; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import ru.practicum.shareit.exception.EmptyIdException; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.exception.NotUniqueEmailException; +import ru.practicum.shareit.user.contracts.UserRepositoryInterface; +import ru.practicum.shareit.user.dto.UserCreateDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.dto.UserUpdateDto; +import ru.practicum.shareit.user.model.User; + +import java.util.Optional; +import java.util.Random; + +@SpringBootTest +@ExtendWith(MockitoExtension.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class UserServiceTest { + + @Autowired + UserRepositoryInterface userRepository; + + @Autowired + UserService userService; + + @Test + void create() { + UserCreateDto userCreateDto = makeUserCreateDto(); + + UserDto userDto = userService.create(userCreateDto); + + Optional user = userRepository.findById(userDto.getId()); + + Assertions.assertTrue(user.isPresent()); + } + + @Test + void createWithExistingEmailReturnThrow() { + UserCreateDto userCreateDto = makeUserCreateDto(); + + userService.create(userCreateDto); + + Assertions.assertThrowsExactly( + NotUniqueEmailException.class, + () -> userService.create(userCreateDto) + ); + } + + @Test + void update() { + UserCreateDto userCreateDto = makeUserCreateDto(); + + UserDto userDto = userService.create(userCreateDto); + + String userNewName = "user new name"; + + userService.update( + new UserUpdateDto( + userDto.getId(), + userNewName, + userDto.getEmail() + ) + ); + + Optional user = userRepository.findById(userDto.getId()); + + Assertions.assertTrue(user.isPresent()); + Assertions.assertEquals(userNewName, user.get().getName()); + } + + @Test + void updateWithWrongUserIdReturnThrow() { + UserCreateDto userCreateDto = makeUserCreateDto(); + + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> userService.update( + new UserUpdateDto( + new Random().nextLong(), + userCreateDto.getName(), + userCreateDto.getEmail() + ) + ) + ); + } + + @Test + void updateWithNonUniqueEmailReturnThrow() { + + UserDto userDto1 = userService.create(makeUserCreateDto()); + UserDto userDto2 = userService.create(makeUserCreateDto()); + + Assertions.assertThrowsExactly( + NotUniqueEmailException.class, + () -> userService.update( + new UserUpdateDto( + userDto2.getId(), + userDto2.getName(), + userDto1.getEmail() + ) + ) + ); + } + + @Test + void updateWithIdIsNullReturnThrow() { + + UserDto userDto = userService.create(makeUserCreateDto()); + + Assertions.assertThrowsExactly( + EmptyIdException.class, + () -> userService.update( + new UserUpdateDto( + null, + userDto.getName(), + userDto.getEmail() + ) + ) + ); + } + + @Test + void findItemById() { + UserCreateDto userCreateDto = makeUserCreateDto(); + + UserDto userDto = userService.create(userCreateDto); + UserDto user = userService.findById(userDto.getId()); + + Assertions.assertNotNull(user); + Assertions.assertEquals(user.getId(), userDto.getId()); + } + + @Test + void findItemByIdReturnThrow() { + Assertions.assertThrowsExactly( + NotFoundException.class, + () -> userService.findById(new Random().nextLong()) + ); + } + + @Test + void delete() { + UserCreateDto userCreateDto = makeUserCreateDto(); + + UserDto userDto = userService.create(userCreateDto); + Optional userBeforeDelete = userRepository.findById(userDto.getId()); + + userService.deleteById(userDto.getId()); + + Optional userAfterDelete = userRepository.findById(userDto.getId()); + + Assertions.assertTrue(userBeforeDelete.isPresent()); + Assertions.assertFalse(userAfterDelete.isPresent()); + } + + private UserCreateDto makeUserCreateDto() { + long id = new Random().nextLong(); + + return new UserCreateDto( + "username #" + id, + "user-" + id + "@yandex.net" + ); + } + +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java b/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java deleted file mode 100644 index 6593410..0000000 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingCreateDto.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.practicum.shareit.booking.dto; - -import jakarta.annotation.Nullable; -import jakarta.validation.constraints.AssertTrue; -import jakarta.validation.constraints.Future; -import jakarta.validation.constraints.FutureOrPresent; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import ru.practicum.shareit.booking.model.BookingStatus; - -import java.time.LocalDateTime; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class BookingCreateDto { - - @Nullable - private Long id; - - @NotNull - @FutureOrPresent - private LocalDateTime start; - - @NotNull - @Future - private LocalDateTime end; - - private Long itemId; - - private Long bookerId; - - @Nullable - private BookingStatus bookingStatus; - - @AssertTrue - boolean isStartBeforeEnd() { - return start != null - && end != null - && start.isBefore(end); - } -} 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 d149fdb..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.practicum.shareit.request; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@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/resources/application-test.properties b/src/main/resources/application-test.properties deleted file mode 100644 index 0768115..0000000 --- a/src/main/resources/application-test.properties +++ /dev/null @@ -1,13 +0,0 @@ -spring.jpa.hibernate.ddl-auto=none -spring.jpa.properties.hibernate.format_sql=true -spring.sql.init.mode=always - -logging.level.org.springframework.orm.jpa=INFO -logging.level.org.springframework.transaction=INFO -logging.level.org.springframework.transaction.interceptor=TRACE -logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG - -spring.datasource.driverClassName=org.h2.Driver -spring.datasource.url=jdbc:h2:mem:shareit;MODE=PostgreSQL -spring.datasource.username=shareit -spring.datasource.password=shareit