Skip to content

Commit 5f03618

Browse files
committed
add item requests and gateway, implement tests, fix review issues
1 parent 902204e commit 5f03618

97 files changed

Lines changed: 2313 additions & 286 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

gateway/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM eclipse-temurin:21-jre-jammy
2+
VOLUME /tmp
3+
ARG JAR_FILE=target/*.jar
4+
COPY ${JAR_FILE} app.jar
5+
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]

gateway/pom.xml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>ru.practicum</groupId>
7+
<artifactId>shareit</artifactId>
8+
<version>0.0.1-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>shareit-gateway</artifactId>
12+
<version>0.0.1-SNAPSHOT</version>
13+
14+
<name>ShareIt Gateway</name>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>org.springframework.boot</groupId>
19+
<artifactId>spring-boot-starter-web</artifactId>
20+
</dependency>
21+
22+
<dependency>
23+
<groupId>org.springframework.boot</groupId>
24+
<artifactId>spring-boot-starter-validation</artifactId>
25+
</dependency>
26+
27+
<dependency>
28+
<groupId>org.springframework.boot</groupId>
29+
<artifactId>spring-boot-starter-actuator</artifactId>
30+
</dependency>
31+
32+
<dependency>
33+
<groupId>org.hibernate.validator</groupId>
34+
<artifactId>hibernate-validator</artifactId>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>org.apache.httpcomponents.client5</groupId>
39+
<artifactId>httpclient5</artifactId>
40+
</dependency>
41+
42+
<dependency>
43+
<groupId>org.springframework.boot</groupId>
44+
<artifactId>spring-boot-configuration-processor</artifactId>
45+
<optional>true</optional>
46+
</dependency>
47+
48+
<dependency>
49+
<groupId>org.projectlombok</groupId>
50+
<artifactId>lombok</artifactId>
51+
<optional>true</optional>
52+
</dependency>
53+
54+
<dependency>
55+
<groupId>org.springframework.boot</groupId>
56+
<artifactId>spring-boot-starter-test</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
</dependencies>
60+
61+
<build>
62+
<plugins>
63+
<plugin>
64+
<groupId>org.springframework.boot</groupId>
65+
<artifactId>spring-boot-maven-plugin</artifactId>
66+
</plugin>
67+
</plugins>
68+
</build>
69+
</project>

src/main/java/ru/practicum/shareit/ShareItApp.java renamed to gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55

66
@SpringBootApplication
7-
public class ShareItApp {
8-
7+
public class ShareItGateway {
98
public static void main(String[] args) {
10-
SpringApplication.run(ShareItApp.class, args);
9+
SpringApplication.run(ShareItGateway.class, args);
1110
}
12-
1311
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package ru.practicum.shareit.booking;
2+
3+
import java.util.Map;
4+
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.boot.web.client.RestTemplateBuilder;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
10+
import org.springframework.stereotype.Service;
11+
import org.springframework.web.util.DefaultUriBuilderFactory;
12+
13+
import ru.practicum.shareit.booking.dto.BookItemRequestDto;
14+
import ru.practicum.shareit.booking.dto.BookingState;
15+
import ru.practicum.shareit.client.BaseClient;
16+
17+
@Service
18+
public class BookingClient extends BaseClient {
19+
private static final String API_PREFIX = "/bookings";
20+
21+
@Autowired
22+
public BookingClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) {
23+
super(
24+
builder
25+
.uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX))
26+
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory())
27+
.build()
28+
);
29+
}
30+
31+
public ResponseEntity<Object> getBookings(long userId, BookingState state, Integer from, Integer size) {
32+
Map<String, Object> parameters = Map.of(
33+
"state", state.name(),
34+
"from", from,
35+
"size", size
36+
);
37+
return get("?state={state}&from={from}&size={size}", userId, parameters);
38+
}
39+
40+
41+
public ResponseEntity<Object> bookItem(long userId, BookItemRequestDto requestDto) {
42+
return post("", userId, requestDto);
43+
}
44+
45+
public ResponseEntity<Object> getBooking(long userId, Long bookingId) {
46+
return get("/" + bookingId, userId);
47+
}
48+
49+
public ResponseEntity<Object> approveBooking(long userId, Long bookingId, boolean approved) {
50+
Map<String, Object> parameters = Map.of("approved", approved);
51+
return patch("/" + bookingId + "?approved={approved}", userId, parameters, null);
52+
}
53+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package ru.practicum.shareit.booking;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.stereotype.Controller;
5+
import org.springframework.validation.annotation.Validated;
6+
import org.springframework.web.bind.annotation.*;
7+
8+
import jakarta.validation.Valid;
9+
import jakarta.validation.constraints.Positive;
10+
import jakarta.validation.constraints.PositiveOrZero;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import ru.practicum.shareit.booking.dto.BookItemRequestDto;
14+
import ru.practicum.shareit.booking.dto.BookingState;
15+
16+
17+
@Controller
18+
@RequestMapping(path = "/bookings")
19+
@RequiredArgsConstructor
20+
@Slf4j
21+
@Validated
22+
public class BookingController {
23+
private final BookingClient bookingClient;
24+
25+
@GetMapping
26+
public ResponseEntity<Object> getBookings(@RequestHeader("X-Sharer-User-Id") long userId,
27+
@RequestParam(name = "state", defaultValue = "all") String stateParam,
28+
@PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from,
29+
@Positive @RequestParam(name = "size", defaultValue = "10") Integer size) {
30+
BookingState state = BookingState.from(stateParam)
31+
.orElseThrow(() -> new IllegalArgumentException("Unknown state: " + stateParam));
32+
log.info("Get booking with state {}, userId={}, from={}, size={}", stateParam, userId, from, size);
33+
return bookingClient.getBookings(userId, state, from, size);
34+
}
35+
36+
@PostMapping
37+
public ResponseEntity<Object> bookItem(@RequestHeader("X-Sharer-User-Id") long userId,
38+
@RequestBody @Valid BookItemRequestDto requestDto) {
39+
log.info("Creating booking {}, userId={}", requestDto, userId);
40+
return bookingClient.bookItem(userId, requestDto);
41+
}
42+
43+
@GetMapping("/{bookingId}")
44+
public ResponseEntity<Object> getBooking(@RequestHeader("X-Sharer-User-Id") long userId,
45+
@PathVariable Long bookingId) {
46+
log.info("Get booking {}, userId={}", bookingId, userId);
47+
return bookingClient.getBooking(userId, bookingId);
48+
}
49+
50+
@PatchMapping("/{bookingId}")
51+
public ResponseEntity<Object> approveBooking(@RequestHeader("X-Sharer-User-Id") long userId,
52+
@PathVariable Long bookingId,
53+
@RequestParam boolean approved) {
54+
log.info("Approve booking {} by userId={}, approved={}", bookingId, userId, approved);
55+
return bookingClient.approveBooking(userId, bookingId, approved);
56+
}
57+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ru.practicum.shareit.booking.dto;
2+
3+
import java.time.LocalDateTime;
4+
5+
import jakarta.validation.constraints.Future;
6+
import jakarta.validation.constraints.FutureOrPresent;
7+
import lombok.AllArgsConstructor;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
@Getter
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
public class BookItemRequestDto {
15+
private long itemId;
16+
@FutureOrPresent
17+
private LocalDateTime start;
18+
@Future
19+
private LocalDateTime end;
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ru.practicum.shareit.booking.dto;
2+
3+
import java.util.Optional;
4+
5+
public enum BookingState {
6+
ALL,
7+
CURRENT,
8+
FUTURE,
9+
PAST,
10+
REJECTED,
11+
WAITING;
12+
13+
public static Optional<BookingState> from(String stringState) {
14+
for (BookingState state : values()) {
15+
if (state.name().equalsIgnoreCase(stringState)) {
16+
return Optional.of(state);
17+
}
18+
}
19+
return Optional.empty();
20+
}
21+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package ru.practicum.shareit.client;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
import org.springframework.http.HttpEntity;
7+
import org.springframework.http.HttpHeaders;
8+
import org.springframework.http.HttpMethod;
9+
import org.springframework.http.MediaType;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.lang.Nullable;
12+
import org.springframework.web.client.HttpStatusCodeException;
13+
import org.springframework.web.client.RestTemplate;
14+
15+
public class BaseClient {
16+
protected final RestTemplate rest;
17+
18+
public BaseClient(RestTemplate rest) {
19+
this.rest = rest;
20+
}
21+
22+
protected ResponseEntity<Object> get(String path) {
23+
return get(path, null, null);
24+
}
25+
26+
protected ResponseEntity<Object> get(String path, long userId) {
27+
return get(path, userId, null);
28+
}
29+
30+
protected ResponseEntity<Object> get(String path, Long userId, @Nullable Map<String, Object> parameters) {
31+
return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null);
32+
}
33+
34+
protected <T> ResponseEntity<Object> post(String path, T body) {
35+
return post(path, null, null, body);
36+
}
37+
38+
protected <T> ResponseEntity<Object> post(String path, long userId, T body) {
39+
return post(path, userId, null, body);
40+
}
41+
42+
protected <T> ResponseEntity<Object> post(String path, Long userId, @Nullable Map<String, Object> parameters, T body) {
43+
return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body);
44+
}
45+
46+
protected <T> ResponseEntity<Object> put(String path, long userId, T body) {
47+
return put(path, userId, null, body);
48+
}
49+
50+
protected <T> ResponseEntity<Object> put(String path, long userId, @Nullable Map<String, Object> parameters, T body) {
51+
return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body);
52+
}
53+
54+
protected <T> ResponseEntity<Object> patch(String path, T body) {
55+
return patch(path, null, null, body);
56+
}
57+
58+
protected <T> ResponseEntity<Object> patch(String path, long userId) {
59+
return patch(path, userId, null, null);
60+
}
61+
62+
protected <T> ResponseEntity<Object> patch(String path, long userId, T body) {
63+
return patch(path, userId, null, body);
64+
}
65+
66+
protected <T> ResponseEntity<Object> patch(String path, Long userId, @Nullable Map<String, Object> parameters, T body) {
67+
return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body);
68+
}
69+
70+
protected ResponseEntity<Object> delete(String path) {
71+
return delete(path, null, null);
72+
}
73+
74+
protected ResponseEntity<Object> delete(String path, long userId) {
75+
return delete(path, userId, null);
76+
}
77+
78+
protected ResponseEntity<Object> delete(String path, Long userId, @Nullable Map<String, Object> parameters) {
79+
return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null);
80+
}
81+
82+
private <T> ResponseEntity<Object> makeAndSendRequest(HttpMethod method, String path, Long userId, @Nullable Map<String, Object> parameters, @Nullable T body) {
83+
HttpEntity<T> requestEntity = new HttpEntity<>(body, defaultHeaders(userId));
84+
85+
ResponseEntity<Object> shareitServerResponse;
86+
try {
87+
if (parameters != null) {
88+
shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters);
89+
} else {
90+
shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class);
91+
}
92+
} catch (HttpStatusCodeException e) {
93+
return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray());
94+
}
95+
return prepareGatewayResponse(shareitServerResponse);
96+
}
97+
98+
private HttpHeaders defaultHeaders(Long userId) {
99+
HttpHeaders headers = new HttpHeaders();
100+
headers.setContentType(MediaType.APPLICATION_JSON);
101+
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
102+
if (userId != null) {
103+
headers.set("X-Sharer-User-Id", String.valueOf(userId));
104+
}
105+
return headers;
106+
}
107+
108+
private static ResponseEntity<Object> prepareGatewayResponse(ResponseEntity<Object> response) {
109+
if (response.getStatusCode().is2xxSuccessful()) {
110+
return response;
111+
}
112+
113+
ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode());
114+
115+
if (response.hasBody()) {
116+
return responseBuilder.body(response.getBody());
117+
}
118+
119+
return responseBuilder.build();
120+
}
121+
}

src/main/java/ru/practicum/shareit/exception/ErrorResponse.java renamed to gateway/src/main/java/ru/practicum/shareit/exception/ErrorResponse.java

File renamed without changes.

0 commit comments

Comments
 (0)