Skip to content

Commit e18e3bb

Browse files
committed
#178: start of api client
1 parent e62878b commit e18e3bb

File tree

6 files changed

+168
-0
lines changed

6 files changed

+168
-0
lines changed

pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@
162162
<artifactId>hamcrest-library</artifactId>
163163
<scope>test</scope>
164164
</dependency>
165+
166+
<!-- for HeimatTime LocalDate (de)serializiation -->
167+
<dependency>
168+
<groupId>com.fasterxml.jackson.datatype</groupId>
169+
<artifactId>jackson-datatype-jsr310</artifactId>
170+
<version>2.18.2</version>
171+
</dependency>
172+
165173
</dependencies>
166174
<build>
167175
<finalName>keeptime-${project.version}</finalName>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package de.doubleslash.keeptime.rest.integration.heimat;
2+
3+
import de.doubleslash.keeptime.rest.integration.heimat.model.HeimatTask;
4+
import de.doubleslash.keeptime.rest.integration.heimat.model.HeimatTime;
5+
import org.springframework.core.ParameterizedTypeReference;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.web.client.RestClient;
8+
import org.springframework.web.util.UriBuilder;
9+
10+
import java.time.LocalDate;
11+
import java.time.format.DateTimeFormatter;
12+
import java.util.List;
13+
14+
public class HeimatAPI {
15+
16+
private final RestClient restClient;
17+
18+
public HeimatAPI(final String baseUrl, final String bearerToken) {
19+
restClient = RestClient.builder()
20+
.baseUrl(baseUrl)
21+
.defaultHeader("X-Client-Identifier", "KeepTime")
22+
.defaultHeader("Authorization", "Bearer " + bearerToken)
23+
.build();
24+
}
25+
26+
public boolean isLoginValid() {
27+
getMyTasks();
28+
return true;
29+
}
30+
31+
public List<HeimatTask> getMyTasks() {
32+
return getMyTasks(null);
33+
}
34+
35+
// GET /my/tasks
36+
public List<HeimatTask> getMyTasks(final LocalDate forDate) {
37+
return restClient.get().uri(uriBuilder -> {
38+
UriBuilder builder = uriBuilder.path("/my/tasks");
39+
if (forDate != null) {
40+
builder.queryParam("date", forDate.format(DateTimeFormatter.ISO_DATE));
41+
}
42+
return builder.build();
43+
}).retrieve().onStatus(HttpStatus.UNAUTHORIZED::equals, (request, response) -> {
44+
throw new UnauthorizedException();
45+
}).body(new ParameterizedTypeReference<>() {});
46+
}
47+
48+
public List<HeimatTime> getMyTimes() {
49+
return getMyTimes(null);
50+
}
51+
52+
// GET /my/times
53+
public List<HeimatTime> getMyTimes(final LocalDate forDate) {
54+
return restClient.get().uri(uriBuilder -> {
55+
UriBuilder builder = uriBuilder.path("/my/times");
56+
if (forDate != null) {
57+
builder.queryParam("date", forDate.format(DateTimeFormatter.ISO_DATE));
58+
}
59+
return builder.build();
60+
}).retrieve().onStatus(HttpStatus.UNAUTHORIZED::equals, (request, response) -> {
61+
throw new UnauthorizedException();
62+
}).body(new ParameterizedTypeReference<>() {});
63+
}
64+
65+
// POST /my/times
66+
public void addMyTime(final HeimatTime heimatTime) {
67+
restClient.post()
68+
.uri(uriBuilder -> {
69+
UriBuilder builder = uriBuilder.path("/my/times");
70+
return builder.build();
71+
})
72+
.header("Content-Type", "application/json")
73+
.body(heimatTime)
74+
.retrieve()
75+
.onStatus(HttpStatus.UNAUTHORIZED::equals, (request, response) -> {
76+
throw new UnauthorizedException();
77+
});
78+
}
79+
80+
// DELETE /my/times/{id}
81+
82+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package de.doubleslash.keeptime.rest.integration.heimat;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
6+
import java.time.LocalDateTime;
7+
import java.time.format.DateTimeFormatter;
8+
import java.util.Base64;
9+
import java.util.Map;
10+
11+
public class JwtDecoder {
12+
13+
public record JWTTokenAttributes(
14+
String header,
15+
String payload,
16+
LocalDateTime expiration
17+
) {}
18+
19+
20+
public static JWTTokenAttributes parse(String bearerToken) {
21+
String token = removeBearerPrefix(bearerToken);
22+
23+
String[] parts = token.split("\\.");
24+
if (parts.length != 3) {
25+
throw new IllegalArgumentException("Invalid JWT token format");
26+
}
27+
28+
String header = new String(Base64.getUrlDecoder().decode(parts[0]));
29+
String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
30+
31+
ObjectMapper mapper = new ObjectMapper();
32+
Map<String, Object> claims = null;
33+
try {
34+
claims = mapper.readValue(payload, Map.class);
35+
} catch (JsonProcessingException e) {
36+
throw new RuntimeException(e);
37+
}
38+
39+
final LocalDateTime expiration = LocalDateTime.parse((String) claims.get("expiration"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
40+
41+
return new JWTTokenAttributes(header, payload, expiration);
42+
}
43+
44+
private static String removeBearerPrefix(String token) {
45+
return token.startsWith("Bearer ") ? token.substring(7) : token;
46+
}
47+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package de.doubleslash.keeptime.rest.integration.heimat;
2+
3+
public class UnauthorizedException extends RuntimeException {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package de.doubleslash.keeptime.rest.integration.heimat.model;
2+
3+
public record HeimatTask(
4+
long id, // int64
5+
String name,
6+
String projectName,
7+
boolean isFavorite,
8+
String bookingHint,
9+
boolean isStartAndEndTimeRequired,
10+
boolean isNoteOptional
11+
) {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package de.doubleslash.keeptime.rest.integration.heimat.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonFormat;
4+
5+
import java.time.LocalDate;
6+
import java.time.LocalTime;
7+
8+
public record HeimatTime(
9+
long taskId, // int64
10+
@JsonFormat(pattern = "yyyy-MM-dd")
11+
LocalDate date, // 2024-01-01
12+
LocalTime start, // 23:59
13+
LocalTime end, // 23:59
14+
int durationInMinutes, // int32
15+
String note,
16+
long id // int64
17+
) {}

0 commit comments

Comments
 (0)