diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..fd1dd0d --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,47 @@ + + + + shareit + ru.practicum + 0.0.1-SNAPSHOT + + 4.0.0 + + shareit-gateway + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + true + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java new file mode 100644 index 0000000..e14acfb --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItGateway { + public static void main(String[] args) { + SpringApplication.run(ShareItGateway.class, args); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java new file mode 100644 index 0000000..4aaaf4f --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -0,0 +1,55 @@ +package ru.practicum.shareit.booking; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.client.BaseClient; +import java.util.Map; + +@Service +public class BookingClient extends BaseClient { + private static final String API_PREFIX = "/bookings"; + + @Autowired + public BookingClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super(builder.uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()).build()); + } + + public ResponseEntity create(Long userId, BookingDto requestDto) { + return post("", userId, requestDto); + } + + public ResponseEntity approve(Long userId, Long bookingId, boolean approved) { + // ИЗМЕНЕНО: используем URI template для корректной передачи boolean параметра + Map parameters = Map.of("approved", approved); + return patch("/" + bookingId + "?approved={approved}", userId, parameters, null); + } + + public ResponseEntity getById(Long userId, Long bookingId) { + return get("/" + bookingId, userId); + } + + public ResponseEntity getAllByBooker(Long userId, String state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state, + "from", from, + "size", size + ); + return get("?state={state}&from={from}&size={size}", userId, parameters); + } + + public ResponseEntity getAllByOwner(Long userId, String state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state, + "from", from, + "size", size + ); + return get("/owner?state={state}&from={from}&size={size}", userId, parameters); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java new file mode 100644 index 0000000..7873330 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,55 @@ +package ru.practicum.shareit.booking; + +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.*; +import ru.practicum.shareit.booking.dto.BookingDto; + +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Validated +public class BookingController { + private final BookingClient bookingClient; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ResponseEntity create(@RequestHeader(USER_ID_HEADER) Long userId, + @Valid @RequestBody BookingDto bookingDto) { + return bookingClient.create(userId, bookingDto); + } + + @PatchMapping("/{bookingId}") + public ResponseEntity approve(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long bookingId, + @RequestParam boolean approved) { + return bookingClient.approve(userId, bookingId, approved); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getById(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long bookingId) { + return bookingClient.getById(userId, bookingId); + } + + @GetMapping + public ResponseEntity getAllByBooker( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(name = "state", defaultValue = "ALL") String state, + @RequestParam(name = "from", defaultValue = "0") Integer from, + @RequestParam(name = "size", defaultValue = "10") Integer size) { + return bookingClient.getAllByBooker(userId, state, from, size); + } + + @GetMapping("/owner") + public ResponseEntity getAllByOwner( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(name = "state", defaultValue = "ALL") String state, + @RequestParam(name = "from", defaultValue = "0") Integer from, + @RequestParam(name = "size", defaultValue = "10") Integer size) { + return bookingClient.getAllByOwner(userId, state, from, size); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java index ffb2238..be71248 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,27 +1,19 @@ package ru.practicum.shareit.booking.dto; -import jakarta.validation.constraints.Future; -import jakarta.validation.constraints.FutureOrPresent; import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.experimental.FieldDefaults; - import java.time.LocalDateTime; @Data -@AllArgsConstructor @NoArgsConstructor +@AllArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class BookingDto { - @NotNull Long itemId; - @NotNull - @FutureOrPresent LocalDateTime start; - @NotNull - @Future LocalDateTime end; } \ No newline at end of file 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..732e73e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,121 @@ +package ru.practicum.shareit.client; + +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; + +import java.util.List; +import java.util.Map; + +public class BaseClient { + protected final RestTemplate rest; + + public BaseClient(RestTemplate rest) { + this.rest = rest; + } + + protected ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + protected ResponseEntity get(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return put(path, userId, null, body); + } + + protected ResponseEntity put(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId) { + return patch(path, userId, null, null); + } + + protected ResponseEntity patch(String path, long userId, T body) { + return patch(path, userId, null, body); + } + + protected ResponseEntity patch(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + } + + protected ResponseEntity delete(String path) { + return delete(path, null, null); + } + + protected ResponseEntity delete(String path, long userId) { + return delete(path, userId, null); + } + + protected ResponseEntity delete(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + } + + 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(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/client/RestTemplateConfig.java b/gateway/src/main/java/ru/practicum/shareit/client/RestTemplateConfig.java new file mode 100644 index 0000000..3fd80d2 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/RestTemplateConfig.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.client; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } +} \ No newline at end of file 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..5716350 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -0,0 +1,31 @@ +package ru.practicum.shareit.exception; + +import jakarta.validation.ConstraintViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.Map; + +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map handleValidation(final MethodArgumentNotValidException e) { + return Map.of("error", "Ошибка валидации"); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map handleConstraintViolation(final ConstraintViolationException e) { + return Map.of("error", e.getMessage()); + } + + @ExceptionHandler(Throwable.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Map handleThrowable(final Throwable e) { + return Map.of("error", "Непредвиденная ошибка"); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java new file mode 100644 index 0000000..0604c62 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -0,0 +1,59 @@ +package ru.practicum.shareit.item; + +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.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.util.Map; + + +@Service +public class ItemClient extends BaseClient { + private static final String API_PREFIX = "/items"; + + @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(Long userId, ItemDto itemDto) { + return post("", userId, itemDto); + } + + public ResponseEntity update(Long userId, Long itemId, ItemDto itemDto) { + return patch("/" + itemId, userId, itemDto); + } + + public ResponseEntity getById(Long itemId, Long userId) { + return get("/" + itemId, userId); + } + + public ResponseEntity getOwnerItems(Long userId, Integer from, Integer size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("?from={from}&size={size}", userId, parameters); + } + + public ResponseEntity search(String text, Integer from, Integer size) { + Map parameters = Map.of( + "text", text, + "from", from, + "size", size + ); + return get("/search?text={text}&from={from}&size={size}", null, parameters); + } + + public ResponseEntity addComment(Long userId, Long itemId, CommentDto commentDto) { + return post("/" + itemId + "/comment", userId, commentDto); + } +} \ No newline at end of file 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..28787b1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,67 @@ +package ru.practicum.shareit.item; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; + +@Controller +@RequestMapping(path = "/items") +@RequiredArgsConstructor +@Slf4j +@Validated +public class ItemController { + private final ItemClient itemClient; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ResponseEntity create(@RequestHeader(USER_ID_HEADER) Long userId, + @Valid @RequestBody ItemDto itemDto) { + log.info("Creating item {} for user {}", itemDto, userId); + return itemClient.create(userId, itemDto); + } + + @PatchMapping("/{itemId}") + public ResponseEntity update(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long itemId, + @RequestBody ItemDto itemDto) { + log.info("Updating item {} for user {}", itemId, userId); + return itemClient.update(userId, itemId, itemDto); + } + + @GetMapping("/{itemId}") + public ResponseEntity getById(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long itemId) { + log.info("Get item {}", itemId); + return itemClient.getById(itemId, userId); + } + + @GetMapping + public ResponseEntity getOwnerItems( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(name = "from", defaultValue = "0") Integer from, + @RequestParam(name = "size", defaultValue = "10") Integer size) { + return itemClient.getOwnerItems(userId, from, size); + } + + @GetMapping("/search") + public ResponseEntity search( + @RequestParam String text, + @RequestParam(name = "from", defaultValue = "0") Integer from, + @RequestParam(name = "size", defaultValue = "10") Integer size) { + return itemClient.search(text, from, size); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity addComment(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long itemId, + @Valid @RequestBody CommentDto commentDto) { + log.info("Add comment to item {} from user {}", itemId, userId); + return itemClient.addComment(userId, itemId, commentDto); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..b70688d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class CommentDto { + Long id; + @NotBlank(message = "Текст комментария не может быть пустым") + String text; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java new file mode 100644 index 0000000..dc6f09f --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ItemDto { + Long id; + @NotBlank + String name; + @NotBlank + String description; + @NotNull + Boolean available; + Long requestId; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/RequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/RequestClient.java new file mode 100644 index 0000000..fc85eb3 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/RequestClient.java @@ -0,0 +1,48 @@ +package ru.practicum.shareit.request; + +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.RequestDto; + +import java.util.Map; + +@Service +public class RequestClient extends BaseClient { + private static final String API_PREFIX = "/requests"; + + @Autowired + public RequestClient(@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, RequestDto dto) { + return post("", userId, dto); + } + + public ResponseEntity getUserRequests(Long userId) { + return get("", userId); + } + + public ResponseEntity getAllRequests(Long userId) { + return get("/all", userId); + } + + public ResponseEntity getById(Long userId, Long requestId) { + return get("/" + requestId, userId); + } + + public ResponseEntity getAllRequests(Long userId, Integer from, Integer size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("/all?from={from}&size={size}", userId, parameters); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/RequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/RequestController.java new file mode 100644 index 0000000..41207c0 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/RequestController.java @@ -0,0 +1,49 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.RequestDto; + +@Controller +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +@Slf4j +@Validated +public class RequestController { + private final RequestClient requestClient; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ResponseEntity create(@RequestHeader(USER_ID_HEADER) Long userId, + @Valid @RequestBody RequestDto dto) { + log.info("Creating request for user {}", userId); + return requestClient.create(userId, dto); + } + + @GetMapping + public ResponseEntity getUserRequests(@RequestHeader(USER_ID_HEADER) Long userId) { + log.info("Get requests for user {}", userId); + return requestClient.getUserRequests(userId); + } + + @GetMapping("/all") + public ResponseEntity getAllRequests( + @RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(name = "from", defaultValue = "0") Integer from, + @RequestParam(name = "size", defaultValue = "10") Integer size) { + log.info("Get all requests from={}, size={} for user {}", from, size, userId); + return requestClient.getAllRequests(userId, from, size); + } + + @GetMapping("/{requestId}") + public ResponseEntity getById(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long requestId) { + log.info("Get request {} for user {}", requestId, userId); + return requestClient.getById(userId, requestId); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java new file mode 100644 index 0000000..51d0c6e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class RequestDto { + @NotBlank(message = "Описание запроса не может быть пустым") + String description; +} \ No newline at end of file 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..5c998cc --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -0,0 +1,42 @@ +package ru.practicum.shareit.user; + +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.UserDto; + +@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 createUser(UserDto userDto) { + return post("", userDto); + } + + public ResponseEntity updateUser(Long userId, UserDto userDto) { + return patch("/" + userId, userId, userDto); + } + + public ResponseEntity getUserById(Long userId) { + return get("/" + userId, userId); + } + + public ResponseEntity getAllUsers() { + return get(""); + } + + public ResponseEntity deleteUser(Long userId) { + return delete("/" + userId, userId); + } +} \ No newline at end of file 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..0299aeb --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,42 @@ +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.*; +import ru.practicum.shareit.user.dto.UserDto; + +@Controller +@RequestMapping(path = "/users") +@RequiredArgsConstructor +@Validated +public class UserController { + private final UserClient userClient; + + @PostMapping + public ResponseEntity create(@Valid @RequestBody UserDto userDto) { + return userClient.createUser(userDto); + } + + @PatchMapping("/{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody UserDto userDto) { + return userClient.updateUser(id, userDto); + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable Long id) { + return userClient.getUserById(id); + } + + @GetMapping + public ResponseEntity getAll() { + return userClient.getAllUsers(); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteById(@PathVariable Long id) { + return userClient.deleteUser(id); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..a87bbd1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class UserDto { + Long id; + @NotBlank + String name; + @NotBlank + @Email + String email; +} \ No newline at end of file diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..3ed19f7 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,5 @@ +server.port=8080 +shareit-server.url=http://localhost:9090 + +management.endpoints.web.exposure.include=health +management.endpoint.health.show-details=always \ No newline at end of file diff --git a/pom.xml b/pom.xml index 585edda..7b3adcd 100644 --- a/pom.xml +++ b/pom.xml @@ -14,69 +14,38 @@ shareit 0.0.1-SNAPSHOT + pom + 21 - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-validation - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.postgresql - postgresql - runtime - - - - com.h2database - h2 - runtime - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - + + gateway + server + - org.springframework.boot - spring-boot-maven-plugin + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 - - - org.projectlombok - lombok - - + checkstyle.xml + true + true + true + + + validate + validate + + check + + + @@ -84,29 +53,7 @@ check - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.3.1 - - checkstyle.xml - true - true - true - - - - - check - - compile - - - - - + \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..4197b43 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,57 @@ + + + + shareit + ru.practicum + 0.0.1-SNAPSHOT + + 4.0.0 + + shareit-server + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + 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/server/src/main/java/ru/practicum/shareit/ShareItApp.java similarity index 100% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItApp.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java similarity index 69% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingController.java index bc4a7fe..4b7eea3 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -4,8 +4,6 @@ import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.dto.BookingResponseDto; - -import jakarta.validation.Valid; import java.util.List; @RestController @@ -17,7 +15,7 @@ public class BookingController { @PostMapping public BookingResponseDto create(@RequestHeader(USER_ID_HEADER) Long userId, - @Valid @RequestBody BookingDto bookingDto) { + @RequestBody BookingDto bookingDto) { return bookingService.create(userId, bookingDto); } @@ -36,13 +34,17 @@ public BookingResponseDto getById(@RequestHeader(USER_ID_HEADER) Long userId, @GetMapping public List getAllByBooker(@RequestHeader(USER_ID_HEADER) Long userId, - @RequestParam(defaultValue = "ALL") String state) { - return bookingService.getAllByBooker(userId, state); + @RequestParam(defaultValue = "ALL") String state, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + return bookingService.getAllByBooker(userId, state, from, size); } @GetMapping("/owner") public List getAllByOwner(@RequestHeader(USER_ID_HEADER) Long userId, - @RequestParam(defaultValue = "ALL") String state) { - return bookingService.getAllByOwner(userId, state); + @RequestParam(defaultValue = "ALL") String state, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + return bookingService.getAllByOwner(userId, state, from, size); } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java index bfbb225..4d8c2c9 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingRepository.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java @@ -1,5 +1,6 @@ package ru.practicum.shareit.booking; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import ru.practicum.shareit.booking.model.Booking; @@ -10,34 +11,34 @@ public interface BookingRepository extends JpaRepository { @Query("select b from Booking b where b.booker.id = ?1 order by b.start desc") - List findUserBookings(Long bookerId); + List findUserBookings(Long bookerId, Pageable page); @Query("select b from Booking b where b.booker.id = ?1 and b.status = ?2 order by b.start desc") - List findUserStatus(Long bookerId, Status status); + List findUserStatus(Long bookerId, Status status, Pageable page); @Query("select b from Booking b where b.booker.id = ?1 and b.start <= ?2 and b.end >= ?2 order by b.start desc") - List findUserCurrent(Long bookerId, LocalDateTime now); + List findUserCurrent(Long bookerId, LocalDateTime now, Pageable page); @Query("select b from Booking b where b.booker.id = ?1 and b.end < ?2 order by b.start desc") - List findUserPast(Long bookerId, LocalDateTime now); + List findUserPast(Long bookerId, LocalDateTime now, Pageable page); @Query("select b from Booking b where b.booker.id = ?1 and b.start > ?2 order by b.start desc") - List findUserFuture(Long bookerId, LocalDateTime now); + List findUserFuture(Long bookerId, LocalDateTime now, Pageable page); @Query("select b from Booking b where b.item.owner.id = ?1 order by b.start desc") - List findOwnerBookings(Long ownerId); + List findOwnerBookings(Long ownerId, Pageable page); @Query("select b from Booking b where b.item.owner.id = ?1 and b.status = ?2 order by b.start desc") - List findOwnerStatus(Long ownerId, Status status); + List findOwnerStatus(Long ownerId, Status status, Pageable page); @Query("select b from Booking b where b.item.owner.id = ?1 and b.start <= ?2 and b.end >= ?2 order by b.start desc") - List findOwnerCurrent(Long ownerId, LocalDateTime now); + List findOwnerCurrent(Long ownerId, LocalDateTime now, Pageable page); @Query("select b from Booking b where b.item.owner.id = ?1 and b.end < ?2 order by b.start desc") - List findOwnerPast(Long ownerId, LocalDateTime now); + List findOwnerPast(Long ownerId, LocalDateTime now, Pageable page); @Query("select b from Booking b where b.item.owner.id = ?1 and b.start > ?2 order by b.start desc") - List findOwnerFuture(Long ownerId, LocalDateTime now); + List findOwnerFuture(Long ownerId, LocalDateTime now, Pageable page); @Query("select b from Booking b where b.item.id = ?1 order by b.start asc") List findItemBookings(Long itemId); 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 88% rename from src/main/java/ru/practicum/shareit/booking/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingService.java index 935b068..635faab 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingService.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingService.java @@ -2,18 +2,16 @@ import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.dto.BookingResponseDto; - import java.util.List; public interface BookingService { - BookingResponseDto create(Long userId, BookingDto bookingDto); BookingResponseDto approve(Long userId, Long bookingId, boolean approved); BookingResponseDto getById(Long userId, Long bookingId); - List getAllByBooker(Long userId, String state); + List getAllByBooker(Long userId, String state, Integer from, Integer size); - List getAllByOwner(Long userId, String state); + List getAllByOwner(Long userId, String state, Integer from, Integer size); } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java similarity index 56% rename from src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java index 7cf0a92..0655acf 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java @@ -1,6 +1,8 @@ package ru.practicum.shareit.booking; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.booking.dto.BookingDto; @@ -14,6 +16,7 @@ import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.user.User; import ru.practicum.shareit.user.UserRepository; + import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -31,11 +34,12 @@ public class BookingServiceImpl implements BookingService { public BookingResponseDto create(Long userId, BookingDto bookingDto) { User booker = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); Item item = itemRepository.findById(bookingDto.getItemId()).orElseThrow(() -> new NotFoundException("Item not found")); - if (!item.getAvailable()) throw new ValidationException("Not available"); - if (item.getOwner().getId().equals(userId)) throw new NotFoundException("Owner cannot book"); + if (!item.getAvailable()) throw new ValidationException("Вещь недоступна"); + if (item.getOwner().getId().equals(userId)) throw new NotFoundException("Владелец не может бронировать"); if (bookingDto.getEnd().isBefore(bookingDto.getStart()) || bookingDto.getEnd().equals(bookingDto.getStart())) { - throw new ValidationException("Wrong dates"); + throw new ValidationException("Неверные даты"); } + Booking booking = new Booking(); booking.setStart(bookingDto.getStart()); booking.setEnd(bookingDto.getEnd()); @@ -48,9 +52,19 @@ public BookingResponseDto create(Long userId, BookingDto bookingDto) { @Transactional @Override public BookingResponseDto approve(Long userId, Long bookingId, boolean approved) { - Booking booking = bookingRepository.findById(bookingId).orElseThrow(() -> new NotFoundException("Not found")); - if (!booking.getItem().getOwner().getId().equals(userId)) throw new ValidationException("Not an owner"); - if (booking.getStatus() != Status.WAITING) throw new ValidationException("Already changed"); + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new NotFoundException("Booking not found")); + + // ИЗМЕНЕНО: Если не владелец пытается подтвердить, бросаем ValidationException (400), + // так как тест "Booking approve by wrong user" не принимает 404. + if (!booking.getItem().getOwner().getId().equals(userId)) { + throw new ValidationException("Not an owner"); + } + + if (booking.getStatus() == Status.APPROVED) { + throw new ValidationException("Already approved"); + } + booking.setStatus(approved ? Status.APPROVED : Status.REJECTED); return BookingMapper.toBookingResponse(bookingRepository.save(booking)); } @@ -65,36 +79,36 @@ public BookingResponseDto getById(Long userId, Long bookingId) { } @Override - public List getAllByBooker(Long userId, String state) { + public List getAllByBooker(Long userId, String state, Integer from, Integer size) { userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); + Pageable page = PageRequest.of(from / size, size); LocalDateTime now = LocalDateTime.now(); - List bookings; - switch (state.toUpperCase()) { - case "ALL": bookings = bookingRepository.findUserBookings(userId); break; - case "CURRENT": bookings = bookingRepository.findUserCurrent(userId, now); break; - case "PAST": bookings = bookingRepository.findUserPast(userId, now); break; - case "FUTURE": bookings = bookingRepository.findUserFuture(userId, now); break; - case "WAITING": bookings = bookingRepository.findUserStatus(userId, Status.WAITING); break; - case "REJECTED": bookings = bookingRepository.findUserStatus(userId, Status.REJECTED); break; - default: throw new ValidationException("Unknown state: " + state); - } - return bookings.stream().map(BookingMapper::toBookingResponse).collect(Collectors.toList()); + List list = switch (state.toUpperCase()) { + case "ALL" -> bookingRepository.findUserBookings(userId, page); + case "CURRENT" -> bookingRepository.findUserCurrent(userId, now, page); + case "PAST" -> bookingRepository.findUserPast(userId, now, page); + case "FUTURE" -> bookingRepository.findUserFuture(userId, now, page); + case "WAITING" -> bookingRepository.findUserStatus(userId, Status.WAITING, page); + case "REJECTED" -> bookingRepository.findUserStatus(userId, Status.REJECTED, page); + default -> throw new ValidationException("Unknown state: " + state); + }; + return list.stream().map(BookingMapper::toBookingResponse).collect(Collectors.toList()); } @Override - public List getAllByOwner(Long userId, String state) { + public List getAllByOwner(Long userId, String state, Integer from, Integer size) { userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); + Pageable page = PageRequest.of(from / size, size); LocalDateTime now = LocalDateTime.now(); - List bookings; - switch (state.toUpperCase()) { - case "ALL": bookings = bookingRepository.findOwnerBookings(userId); break; - case "CURRENT": bookings = bookingRepository.findOwnerCurrent(userId, now); break; - case "PAST": bookings = bookingRepository.findOwnerPast(userId, now); break; - case "FUTURE": bookings = bookingRepository.findOwnerFuture(userId, now); break; - case "WAITING": bookings = bookingRepository.findOwnerStatus(userId, Status.WAITING); break; - case "REJECTED": bookings = bookingRepository.findOwnerStatus(userId, Status.REJECTED); break; - default: throw new ValidationException("Unknown state: " + state); - } - return bookings.stream().map(BookingMapper::toBookingResponse).collect(Collectors.toList()); + List list = switch (state.toUpperCase()) { + case "ALL" -> bookingRepository.findOwnerBookings(userId, page); + case "CURRENT" -> bookingRepository.findOwnerCurrent(userId, now, page); + case "PAST" -> bookingRepository.findOwnerPast(userId, now, page); + case "FUTURE" -> bookingRepository.findOwnerFuture(userId, now, page); + case "WAITING" -> bookingRepository.findOwnerStatus(userId, Status.WAITING, page); + case "REJECTED" -> bookingRepository.findOwnerStatus(userId, Status.REJECTED, page); + default -> throw new ValidationException("Unknown state: " + state); + }; + return list.stream().map(BookingMapper::toBookingResponse).collect(Collectors.toList()); } } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java new file mode 100644 index 0000000..b7e8adc --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class BookingDto { + Long itemId; + LocalDateTime start; + LocalDateTime end; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingResponseDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/mapper/BookingMapper.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/Status.java b/server/src/main/java/ru/practicum/shareit/booking/model/Status.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/Status.java rename to server/src/main/java/ru/practicum/shareit/booking/model/Status.java diff --git a/src/main/java/ru/practicum/shareit/exception/ConflictException.java b/server/src/main/java/ru/practicum/shareit/exception/ConflictException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ConflictException.java rename to server/src/main/java/ru/practicum/shareit/exception/ConflictException.java diff --git a/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java new file mode 100644 index 0000000..00c8610 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java @@ -0,0 +1,34 @@ +package ru.practicum.shareit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.Map; + +@RestControllerAdvice +public class ErrorHandler { + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public Map handleNotFound(final NotFoundException e) { + return Map.of("error", e.getMessage()); + } + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map handleValidation(final ValidationException e) { + return Map.of("error", e.getMessage()); + } + + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public Map handleConflict(final ConflictException e) { + return Map.of("error", e.getMessage()); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public Map handleForbidden(final ForbiddenException e) { + return Map.of("error", e.getMessage()); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java b/server/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java new file mode 100644 index 0000000..d8df923 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/exception/ForbiddenException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.exception; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} \ No newline at end of file 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/ValidationException.java b/server/src/main/java/ru/practicum/shareit/exception/ValidationException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ValidationException.java rename to server/src/main/java/ru/practicum/shareit/exception/ValidationException.java diff --git a/src/main/java/ru/practicum/shareit/item/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/CommentRepository.java diff --git a/server/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemController.java new file mode 100644 index 0000000..15599e3 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,49 @@ +package ru.practicum.shareit.item; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import java.util.List; + +@RestController +@RequestMapping("/items") +@RequiredArgsConstructor +public class ItemController { + private final ItemService itemService; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ItemDto create(@RequestHeader(USER_ID_HEADER) Long userId, @RequestBody ItemDto itemDto) { + return itemService.create(userId, itemDto); + } + + @PatchMapping("/{itemId}") + public ItemDto update(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId, @RequestBody ItemDto itemDto) { + return itemService.update(userId, itemId, itemDto); + } + + @GetMapping("/{itemId}") + public ItemDto getById(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId) { + return itemService.getById(itemId, userId); + } + + @GetMapping + public List getAll(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + return itemService.getAllByOwnerId(userId, from, size); + } + + @GetMapping("/search") + public List search(@RequestParam String text, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + return itemService.search(text, from, size); + } + + @PostMapping("/{itemId}/comment") + public CommentDto createComment(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId, @RequestBody CommentDto commentDto) { + return itemService.createComment(userId, itemId, commentDto); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/item/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/ItemRepository.java new file mode 100644 index 0000000..367ba2b --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/ItemRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.shareit.item.model.Item; +import java.util.List; + +public interface ItemRepository extends JpaRepository { + @Query("select i from Item i where i.owner.id = ?1") + List findByOwner(Long userId, Pageable page); + + @Query("select i from Item i " + + "where i.available = true " + + "and (lower(i.name) like lower(concat('%', ?1, '%')) " + + "or lower(i.description) like lower(concat('%', ?1, '%')))") + List search(String text, Pageable page); + + List findAllByRequestId(Long requestId); +} \ No newline at end of file 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 75% rename from src/main/java/ru/practicum/shareit/item/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/ItemService.java index c907238..9d4a853 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemService.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemService.java @@ -6,16 +6,16 @@ import java.util.List; public interface ItemService { - ItemDto create(Long userId, ItemDto itemDto); ItemDto update(Long userId, Long itemId, ItemDto itemDto); ItemDto getById(Long itemId, Long userId); - List getAllByOwnerId(Long ownerId); + List getAllByOwnerId(Long ownerId, Integer from, Integer size); - List search(String text); + List search(String text, Integer from, Integer size); CommentDto createComment(Long userId, Long itemId, CommentDto commentDto); + } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java similarity index 82% rename from src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java index 582c770..ba9a939 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemServiceImpl.java @@ -1,6 +1,8 @@ package ru.practicum.shareit.item; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.booking.BookingRepository; @@ -16,6 +18,7 @@ import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.user.User; import ru.practicum.shareit.user.UserRepository; + import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @@ -34,6 +37,9 @@ public class ItemServiceImpl implements ItemService { public ItemDto create(Long userId, ItemDto itemDto) { User owner = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); Item item = ItemMapper.toItem(itemDto, owner); + if (itemDto.getRequestId() != null) { + item.setRequestId(itemDto.getRequestId()); + } return ItemMapper.toItemDto(itemRepository.save(item)); } @@ -61,8 +67,9 @@ public ItemDto getById(Long itemId, Long userId) { } @Override - public List getAllByOwnerId(Long ownerId) { - return itemRepository.findAllByOwnerId(ownerId).stream().map(item -> { + public List getAllByOwnerId(Long ownerId, Integer from, Integer size) { + Pageable page = PageRequest.of(from / size, size); + return itemRepository.findByOwner(ownerId, page).stream().map(item -> { ItemDto dto = ItemMapper.toItemDto(item); dto.setComments(commentRepository.findAllByItemId(item.getId()).stream() .map(CommentMapper::toCommentDto).collect(Collectors.toList())); @@ -72,9 +79,10 @@ public List getAllByOwnerId(Long ownerId) { } @Override - public List search(String text) { + public List search(String text, Integer from, Integer size) { if (text == null || text.isBlank()) return Collections.emptyList(); - return itemRepository.searchByText(text).stream() + Pageable page = PageRequest.of(from / size, size); + return itemRepository.search(text, page).stream() .map(ItemMapper::toItemDto).collect(Collectors.toList()); } @@ -83,10 +91,17 @@ public List search(String text) { public CommentDto createComment(Long userId, Long itemId, CommentDto commentDto) { User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); Item item = itemRepository.findById(itemId).orElseThrow(() -> new NotFoundException("Item not found")); - List userBookings = bookingRepository.findUserPast(userId, LocalDateTime.now()); - boolean hasFinishedBooking = userBookings.stream() - .anyMatch(b -> b.getItem().getId().equals(itemId) && b.getStatus() == Status.APPROVED); - if (!hasFinishedBooking) throw new ValidationException("No finished bookings"); + + List userBookings = bookingRepository.findUserBookings(userId, Pageable.unpaged()); + + + boolean canComment = userBookings.stream() + .anyMatch(b -> b.getItem().getId().equals(itemId) + && b.getStatus() == Status.APPROVED + && !b.getEnd().isAfter(LocalDateTime.now())); + + if (!canComment) throw new ValidationException("Ошибка валидации"); + Comment comment = new Comment(); comment.setText(commentDto.getText()); comment.setItem(item); 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 83% 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 index f50e263..a277516 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -1,9 +1,7 @@ package ru.practicum.shareit.item.dto; -import jakarta.validation.constraints.NotBlank; import lombok.*; import lombok.experimental.FieldDefaults; - import java.time.LocalDateTime; @Data @@ -12,7 +10,6 @@ @FieldDefaults(level = AccessLevel.PRIVATE) public class CommentDto { Long id; - @NotBlank String text; String authorName; LocalDateTime created; 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 81% 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 3a66de4..b6d5621 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.*; import lombok.experimental.FieldDefaults; import java.util.List; @@ -12,18 +10,18 @@ @FieldDefaults(level = AccessLevel.PRIVATE) public class ItemDto { Long id; - @NotBlank String name; - @NotBlank String description; - @NotNull Boolean available; BookingShortDto lastBooking; BookingShortDto nextBooking; List comments; + Long requestId; + Long ownerId; @Data @AllArgsConstructor + @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public static class BookingShortDto { Long id; 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 50% 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 index e368b4d..19f0afb 100644 --- a/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/mapper/ItemMapper.java @@ -14,18 +14,20 @@ public static ItemDto toItemDto(Item item) { item.getAvailable(), null, null, - new ArrayList<>() + new ArrayList<>(), + item.getRequestId(), + item.getOwner() != null ? item.getOwner().getId() : null ); } - public static Item toItem(ItemDto itemDto, User owner) { - return new Item( - itemDto.getId(), - itemDto.getName(), - itemDto.getDescription(), - itemDto.getAvailable(), - owner, - null - ); + public static Item toItem(ItemDto dto, User owner) { + Item item = new Item(); + item.setId(dto.getId()); + item.setName(dto.getName()); + item.setDescription(dto.getDescription()); + item.setAvailable(dto.getAvailable()); + item.setOwner(owner); + item.setRequestId(dto.getRequestId()); + return item; } } \ No newline at end of file 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/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java new file mode 100644 index 0000000..fb3ef03 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.request.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.user.User; +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +@Entity +@Table(name = "requests") +public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(nullable = false) + String description; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "requestor_id", nullable = false) + User requestor; + + @Column(nullable = false) + LocalDateTime created; +} \ No newline at end of file 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..522ba7f --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.util.List; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + private final ItemRequestService requestService; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ItemRequestDto create(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestBody ItemRequestDto dto) { + return requestService.create(userId, dto); + } + + @GetMapping + public List getUserRequests(@RequestHeader(USER_ID_HEADER) Long userId) { + return requestService.getUserRequests(userId); + } + + @GetMapping("/all") + public List getAllRequests(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size) { + return requestService.getAllRequests(userId, from, size); + } + + @GetMapping("/{requestId}") + public ItemRequestDto getById(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long requestId) { + return requestService.getById(userId, requestId); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestRepository.java new file mode 100644 index 0000000..13baebe --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestRepository.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.request; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.shareit.request.model.ItemRequest; +import java.util.List; + +public interface ItemRequestRepository extends JpaRepository { + List findAllByRequestorIdOrderByCreatedDesc(Long userId); + + @Query("select r from ItemRequest r where r.requestor.id <> ?1 order by r.created desc") + List findOthers(Long userId, Pageable page); +} \ No newline at end of file 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..1f62b5b --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.request; + +import ru.practicum.shareit.request.dto.ItemRequestDto; +import java.util.List; + +public interface ItemRequestService { + ItemRequestDto create(Long userId, ItemRequestDto dto); + + List getUserRequests(Long userId); + + List getAllRequests(Long userId, Integer from, Integer size); + + ItemRequestDto getById(Long userId, Long requestId); +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java new file mode 100644 index 0000000..405da9c --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java @@ -0,0 +1,77 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.exception.NotFoundException; +import ru.practicum.shareit.item.ItemRepository; +import ru.practicum.shareit.item.mapper.ItemMapper; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.UserRepository; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ItemRequestServiceImpl implements ItemRequestService { + private final ItemRequestRepository requestRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + + @Transactional + @Override + public ItemRequestDto create(Long userId, ItemRequestDto dto) { + User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); + ItemRequest request = new ItemRequest(); + request.setDescription(dto.getDescription()); + request.setRequestor(user); + request.setCreated(LocalDateTime.now()); + return toDto(requestRepository.save(request)); + } + + @Override + public List getUserRequests(Long userId) { + userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); + return requestRepository.findAllByRequestorIdOrderByCreatedDesc(userId) + .stream().map(this::toDto).collect(Collectors.toList()); + } + + @Override + public List getAllRequests(Long userId, Integer from, Integer size) { + Pageable page = PageRequest.of(from / size, size); + return requestRepository.findOthers(userId, page) + .stream().map(this::toDto).collect(Collectors.toList()); + } + + @Override + public ItemRequestDto getById(Long userId, Long requestId) { + userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); + ItemRequest request = requestRepository.findById(requestId) + .orElseThrow(() -> new NotFoundException("Request not found")); + return toDto(request); + } + + private ItemRequestDto toDto(ItemRequest request) { + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(request.getId()); + dto.setDescription(request.getDescription()); + dto.setCreated(request.getCreated()); + + + List items = itemRepository.findAllByRequestId(request.getId()); + if (items != null && !items.isEmpty()) { + dto.setItems(items.stream().map(ItemMapper::toItemDto).collect(Collectors.toList())); + } else { + dto.setItems(new ArrayList<>()); + } + + return dto; + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..1d475e4 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.request.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.item.dto.ItemDto; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ItemRequestDto { + Long id; + String description; + LocalDateTime created; + List items; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/User.java b/server/src/main/java/ru/practicum/shareit/user/User.java similarity index 54% rename from src/main/java/ru/practicum/shareit/user/User.java rename to server/src/main/java/ru/practicum/shareit/user/User.java index a976191..9c00767 100644 --- a/src/main/java/ru/practicum/shareit/user/User.java +++ b/server/src/main/java/ru/practicum/shareit/user/User.java @@ -1,23 +1,15 @@ package ru.practicum.shareit.user; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import jakarta.persistence.*; +import lombok.*; import lombok.experimental.FieldDefaults; @Data -@Entity -@Table(name = "users") -@NoArgsConstructor @AllArgsConstructor +@NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) +@Entity +@Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 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 d9f7da2..002d783 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserController.java @@ -3,8 +3,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.user.dto.UserDto; - -import jakarta.validation.Valid; import java.util.List; @RestController @@ -14,7 +12,7 @@ public class UserController { private final UserService userService; @PostMapping - public UserDto create(@Valid @RequestBody UserDto userDto) { + public UserDto create(@RequestBody UserDto userDto) { return userService.create(userDto); } diff --git a/src/main/java/ru/practicum/shareit/user/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/UserRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/UserRepository.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 99% rename from src/main/java/ru/practicum/shareit/user/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/UserService.java index 993b0a3..e0d9f76 100644 --- a/src/main/java/ru/practicum/shareit/user/UserService.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserService.java @@ -4,7 +4,6 @@ import java.util.List; public interface UserService { - UserDto create(UserDto userDto); UserDto update(Long id, UserDto userDto); diff --git a/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java similarity index 55% rename from src/main/java/ru/practicum/shareit/user/UserServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java index 78c835d..33a5f22 100644 --- a/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserServiceImpl.java @@ -9,7 +9,6 @@ import ru.practicum.shareit.user.mapper.UserMapper; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @Service @@ -21,8 +20,7 @@ public class UserServiceImpl implements UserService { @Transactional @Override public UserDto create(UserDto userDto) { - Optional existingUser = userRepository.findByEmail(userDto.getEmail()); - if (existingUser.isPresent()) { + if (userRepository.findByEmail(userDto.getEmail()).isPresent()) { throw new ConflictException("Email уже используется"); } User user = UserMapper.toUser(userDto); @@ -32,35 +30,29 @@ public UserDto create(UserDto userDto) { @Transactional @Override public UserDto update(Long id, UserDto userDto) { - User userToUpdate = userRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Пользователь не найден")); - + User user = userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("User not found")); if (userDto.getEmail() != null && !userDto.getEmail().isBlank()) { - Optional userWithSameEmail = userRepository.findByEmail(userDto.getEmail()); - if (userWithSameEmail.isPresent() && !userWithSameEmail.get().getId().equals(id)) { - throw new ConflictException("Email уже используется другим пользователем"); - } - userToUpdate.setEmail(userDto.getEmail()); - } - if (userDto.getName() != null && !userDto.getName().isBlank()) { - userToUpdate.setName(userDto.getName()); + userRepository.findByEmail(userDto.getEmail()) + .ifPresent(u -> { + if (!u.getId().equals(id)) throw new ConflictException("Email busy"); + }); + user.setEmail(userDto.getEmail()); } - - return UserMapper.toUserDto(userRepository.save(userToUpdate)); + if (userDto.getName() != null) user.setName(userDto.getName()); + return UserMapper.toUserDto(userRepository.save(user)); } @Override public UserDto getById(Long id) { - User user = userRepository.findById(id) - .orElseThrow(() -> new NotFoundException("Пользователь не найден")); - return UserMapper.toUserDto(user); + return UserMapper.toUserDto(userRepository.findById(id) + .orElseThrow(() -> new NotFoundException("User not found"))); } @Override public List getAll() { return userRepository.findAll().stream() - .map(UserMapper::toUserDto) - .collect(Collectors.toList()); + .map(UserMapper::toUserDto).collect(Collectors.toList()); } @Transactional diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..ff30d85 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.user.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class UserDto { + Long id; + String name; + String email; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java index 26b1b95..d4913ad 100644 --- a/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java +++ b/server/src/main/java/ru/practicum/shareit/user/mapper/UserMapper.java @@ -1,7 +1,7 @@ package ru.practicum.shareit.user.mapper; -import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.user.User; +import ru.practicum.shareit.user.dto.UserDto; public class UserMapper { public static UserDto toUserDto(User user) { diff --git a/src/main/resources/application-test.properties b/server/src/main/resources/application-test.properties similarity index 100% rename from src/main/resources/application-test.properties rename to server/src/main/resources/application-test.properties diff --git a/src/main/resources/application.properties b/server/src/main/resources/application.properties similarity index 70% rename from src/main/resources/application.properties rename to server/src/main/resources/application.properties index 56b6039..4b4222e 100644 --- a/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,14 +1,14 @@ -server.port=8080 +server.port=9090 spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:h2:mem:shareit;DB_CLOSE_DELAY=-1} spring.datasource.username=${SPRING_DATASOURCE_USERNAME:sa} spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:} - -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=false spring.sql.init.mode=always management.endpoints.web.exposure.include=health -management.endpoint.health.show-details=always \ No newline at end of file +management.endpoint.health.show-details=always +logging.level.web=DEBUG diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 82% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql index 2e2e066..46da8ae 100644 --- a/src/main/resources/schema.sql +++ b/server/src/main/resources/schema.sql @@ -38,4 +38,12 @@ CREATE TABLE comments ( created TIMESTAMP WITHOUT TIME ZONE NOT NULL, CONSTRAINT fk_comment_item FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE, CONSTRAINT fk_comment_author FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS requests ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + description VARCHAR(1000) NOT NULL, + requestor_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + CONSTRAINT fk_request_requestor FOREIGN KEY (requestor_id) REFERENCES users(id) ON DELETE CASCADE ); \ No newline at end of file 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/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java b/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java deleted file mode 100644 index d4d9f03..0000000 --- a/src/main/java/ru/practicum/shareit/exception/ErrorHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.practicum.shareit.exception; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import java.util.Map; - -@Slf4j -@RestControllerAdvice -public class ErrorHandler { - - @ExceptionHandler - @ResponseStatus(HttpStatus.NOT_FOUND) - public Map handleNotFound(final NotFoundException e) { - log.error("404 Not Found: {}", e.getMessage()); - return Map.of("error", e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.CONFLICT) - public Map handleConflict(final ConflictException e) { - log.error("409 Conflict: {}", e.getMessage()); - return Map.of("error", e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.BAD_REQUEST) - public Map handleValidation(final ValidationException e) { - log.error("400 Bad Request (Validation): {}", e.getMessage()); - return Map.of("error", e.getMessage()); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.BAD_REQUEST) - public Map handleMethodArgumentNotValid(final MethodArgumentNotValidException e) { - log.error("400 Bad Request (Argument Not Valid): {}", e.getMessage()); - return Map.of("error", "Ошибка валидации данных"); - } - - @ExceptionHandler - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public Map handleThrowable(final Throwable e) { - log.error("500 Internal Server Error: ", e); - return Map.of("error", "Произошла непредвиденная ошибка"); - } -} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/src/main/java/ru/practicum/shareit/item/ItemController.java deleted file mode 100644 index d667bda..0000000 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.practicum.shareit.item; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; -import ru.practicum.shareit.item.dto.CommentDto; -import ru.practicum.shareit.item.dto.ItemDto; - -import jakarta.validation.Valid; -import java.util.List; - -@RestController -@RequestMapping("/items") -@RequiredArgsConstructor -public class ItemController { - private final ItemService itemService; - private static final String SHARER_USER_ID = "X-Sharer-User-Id"; - - @PostMapping - public ItemDto create(@RequestHeader(SHARER_USER_ID) Long userId, - @Valid @RequestBody ItemDto itemDto) { - return itemService.create(userId, itemDto); - } - - @PatchMapping("/{itemId}") - public ItemDto update(@RequestHeader(SHARER_USER_ID) Long userId, - @PathVariable Long itemId, - @RequestBody ItemDto itemDto) { - return itemService.update(userId, itemId, itemDto); - } - - @GetMapping - public List getAllByOwner(@RequestHeader(SHARER_USER_ID) Long userId) { - return itemService.getAllByOwnerId(userId); - } - - @GetMapping("/search") - public List search(@RequestParam String text) { - return itemService.search(text); - } - - @GetMapping("/{itemId}") - public ItemDto getById(@RequestHeader("X-Sharer-User-Id") Long userId, - @PathVariable Long itemId) { - return itemService.getById(itemId, userId); - } - - @PostMapping("/{itemId}/comment") - public CommentDto createComment(@RequestHeader(SHARER_USER_ID) Long userId, - @PathVariable Long itemId, - @Valid @RequestBody CommentDto commentDto) { - return itemService.createComment(userId, itemId, commentDto); - } -} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/ItemRepository.java b/src/main/java/ru/practicum/shareit/item/ItemRepository.java deleted file mode 100644 index a3839c1..0000000 --- a/src/main/java/ru/practicum/shareit/item/ItemRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.practicum.shareit.item; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import ru.practicum.shareit.item.model.Item; - -import java.util.List; - -public interface ItemRepository extends JpaRepository { - - List findAllByOwnerId(Long ownerId); - - @Query("SELECT it FROM Item it " + - "WHERE it.available = true " + - "AND (LOWER(it.name) LIKE LOWER(CONCAT('%', :text, '%')) " + - "OR LOWER(it.description) LIKE LOWER(CONCAT('%', :text, '%')))") - List searchByText(@Param("text") String text); - -} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java deleted file mode 100644 index 95d6f23..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequest { -} 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 f405a54..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ /dev/null @@ -1,10 +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/java/ru/practicum/shareit/user/dto/UserDto.java b/src/main/java/ru/practicum/shareit/user/dto/UserDto.java deleted file mode 100644 index 8efd577..0000000 --- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ /dev/null @@ -1,29 +0,0 @@ -package ru.practicum.shareit.user.dto; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.NoArgsConstructor; -import lombok.experimental.FieldDefaults; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@FieldDefaults(level = AccessLevel.PRIVATE) -public class UserDto { - Long id; - - @NotBlank(message = "Имя не может быть пустым") - - String name; - - @NotNull(message = "Email не может быть null") - - @Email(message = "Некорректный формат email") - - String email; -} \ No newline at end of file