more error handling and only get requests

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2024-08-16 23:05:05 +02:00
parent 3e7eeecc1b
commit d2dd2fb129
11 changed files with 154 additions and 9 deletions

View File

@@ -0,0 +1,14 @@
package dev.mars3142.fhq.timezone_service.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
public class AppConfig {
@Bean
public RestClient restClient() {
return RestClient.create();
}
}

View File

@@ -0,0 +1,9 @@
package dev.mars3142.fhq.timezone_service.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
}

View File

@@ -0,0 +1,8 @@
package dev.mars3142.fhq.timezone_service.timezone.domain.entities.response;
public record TimeApiTimezoneZoneResponse(Interval dstInterval) {
public record Interval(String dstName) {
}
}

View File

@@ -0,0 +1,7 @@
package dev.mars3142.fhq.timezone_service.timezone.domain.entities.response;
public record WorldTimeApiIpResponse(String utc_offset, String timezone, int day_of_week, int day_of_year, String datetime,
String utc_datetime, int unixtime, int raw_offset, int week_number, boolean dst,
String abbreviation, int dst_offset, String dst_from, String dst_until, String client_ip) {
}

View File

@@ -0,0 +1,7 @@
package dev.mars3142.fhq.timezone_service.timezone.domain.model.response;
import java.util.List;
public record LocationResponse(int count, List<String> locations) {
}

View File

@@ -1,5 +1,5 @@
package dev.mars3142.fhq.timezone_service.timezone.domain.model.response; package dev.mars3142.fhq.timezone_service.timezone.domain.model.response;
public record TimeZoneResponse(String timezone) { public record TimeZoneResponse(String timezone, String abbreviation, String posix_tz) {
} }

View File

@@ -1,5 +1,16 @@
package dev.mars3142.fhq.timezone_service.timezone.service; package dev.mars3142.fhq.timezone_service.timezone.service;
import dev.mars3142.fhq.timezone_service.timezone.domain.entities.response.TimeApiTimezoneZoneResponse;
import dev.mars3142.fhq.timezone_service.timezone.domain.entities.response.WorldTimeApiIpResponse;
import java.util.List;
public interface TimeZoneService { public interface TimeZoneService {
WorldTimeApiIpResponse getTimeZoneInfoByIp();
TimeApiTimezoneZoneResponse getTimeZoneInfo(String timezone);
String getPosixTimeZone(String timezone); String getPosixTimeZone(String timezone);
List<String> getLocations(String area);
} }

View File

@@ -1,18 +1,60 @@
package dev.mars3142.fhq.timezone_service.timezone.service.impl; package dev.mars3142.fhq.timezone_service.timezone.service.impl;
import dev.mars3142.fhq.timezone_service.exceptions.NotFoundException;
import dev.mars3142.fhq.timezone_service.timezone.domain.entities.response.TimeApiTimezoneZoneResponse;
import dev.mars3142.fhq.timezone_service.timezone.domain.entities.response.WorldTimeApiIpResponse;
import dev.mars3142.fhq.timezone_service.timezone.service.TimeZoneService; import dev.mars3142.fhq.timezone_service.timezone.service.TimeZoneService;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.val; import lombok.val;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service @Service
@RequiredArgsConstructor
public class TimeZoneServiceImpl implements TimeZoneService { public class TimeZoneServiceImpl implements TimeZoneService {
private final RestClient restClient;
@Override
public WorldTimeApiIpResponse getTimeZoneInfoByIp() {
return restClient
.get()
.uri("https://worldtimeapi.org/api/ip")
.retrieve()
.body(WorldTimeApiIpResponse.class);
}
@Override
public TimeApiTimezoneZoneResponse getTimeZoneInfo(String timezone) {
return restClient
.get()
.uri(builder -> builder
.scheme("https")
.host("timeapi.io")
.path("api/timezone/zone")
.queryParam("timeZone", timezone)
.build())
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new NotFoundException();
})
.body(TimeApiTimezoneZoneResponse.class);
}
@Override @Override
public String getPosixTimeZone(String timezone) { public String getPosixTimeZone(String timezone) {
val filename = Path.of("/usr/share/zoneinfo/" + timezone); val filename = Path.of("/usr/share/zoneinfo/" + timezone);
if (!filename.toFile().exists()) {
throw new NotFoundException();
}
try { try {
val bytes = Files.readAllBytes(filename); val bytes = Files.readAllBytes(filename);
val content = new String(bytes).split("\n"); val content = new String(bytes).split("\n");
@@ -21,4 +63,20 @@ public class TimeZoneServiceImpl implements TimeZoneService {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@Override
public List<String> getLocations(String area) {
val directory = new File("/usr/share/zoneinfo/" + area);
if (!directory.exists()) {
throw new NotFoundException();
}
return Stream.of(Objects.requireNonNull(directory.listFiles()))
.filter(file -> !file.isDirectory())
.map(file -> {
val path = file.getPath().split("/");
return path[path.length - 2] + "/" + path[path.length - 1];
})
.sorted()
.toList();
}
} }

View File

@@ -1,12 +1,12 @@
package dev.mars3142.fhq.timezone_service.timezone.web.controllers; package dev.mars3142.fhq.timezone_service.timezone.web.controllers;
import dev.mars3142.fhq.timezone_service.timezone.domain.model.request.TimeZoneRequest; import dev.mars3142.fhq.timezone_service.timezone.domain.model.response.LocationResponse;
import dev.mars3142.fhq.timezone_service.timezone.domain.model.response.TimeZoneResponse; import dev.mars3142.fhq.timezone_service.timezone.domain.model.response.TimeZoneResponse;
import dev.mars3142.fhq.timezone_service.timezone.service.TimeZoneService; import dev.mars3142.fhq.timezone_service.timezone.service.TimeZoneService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.val; import lombok.val;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -17,9 +17,24 @@ public class TimeZoneController {
private final TimeZoneService timeZoneService; private final TimeZoneService timeZoneService;
@PostMapping() @GetMapping
public TimeZoneResponse getTimeZone(@RequestBody TimeZoneRequest request) { public TimeZoneResponse getTimeZone() {
val timezone = timeZoneService.getPosixTimeZone(request.timezone()); val timezoneInfo = timeZoneService.getTimeZoneInfoByIp();
return new TimeZoneResponse(timezone); val posix = timeZoneService.getPosixTimeZone(timezoneInfo.timezone());
return new TimeZoneResponse(timezoneInfo.timezone(), timezoneInfo.abbreviation(), posix);
}
@GetMapping("{area}")
public LocationResponse getLocations(@PathVariable String area) {
val locations = timeZoneService.getLocations(area);
return new LocationResponse(locations.size(), locations);
}
@GetMapping("{area}/{location}")
public TimeZoneResponse getTimeZone(@PathVariable String area, @PathVariable String location) {
val timezone = area + "/" + location;
val timezoneInfo = timeZoneService.getTimeZoneInfo(timezone);
val posix = timeZoneService.getPosixTimeZone(timezone);
return new TimeZoneResponse(timezone, timezoneInfo.dstInterval().dstName(), posix);
} }
} }

View File

@@ -1 +0,0 @@
spring.application.name=timezone-service

View File

@@ -0,0 +1,17 @@
info:
application:
name: ${spring.application.name}
version: '@project.version@'
spring-cloud-version: '@spring-cloud.version@'
spring-boot-version: '@project.parent.version@'
server:
port: ${PORT:8080}
shutdown: graceful
error:
include-message: on_param
include-stacktrace: on_param
spring:
application:
name: timezone-service