From d2dd2fb1294fe3a00cccb2f6c056cb4753007d37 Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Fri, 16 Aug 2024 23:05:05 +0200 Subject: [PATCH] more error handling and only get requests Signed-off-by: Peter Siegmund --- .../timezone_service/config/AppConfig.java | 14 +++++ .../exceptions/NotFoundException.java | 9 +++ .../response/TimeApiTimezoneZoneResponse.java | 8 +++ .../response/WorldTimeApiIpResponse.java | 7 +++ .../model/response/LocationResponse.java | 7 +++ .../model/response/TimeZoneResponse.java | 2 +- .../timezone/service/TimeZoneService.java | 11 ++++ .../service/impl/TimeZoneServiceImpl.java | 58 +++++++++++++++++++ .../web/controllers/TimeZoneController.java | 29 +++++++--- src/main/resources/application.properties | 1 - src/main/resources/application.yaml | 17 ++++++ 11 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 src/main/java/dev/mars3142/fhq/timezone_service/config/AppConfig.java create mode 100644 src/main/java/dev/mars3142/fhq/timezone_service/exceptions/NotFoundException.java create mode 100644 src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/TimeApiTimezoneZoneResponse.java create mode 100644 src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/WorldTimeApiIpResponse.java create mode 100644 src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/LocationResponse.java delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yaml diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/config/AppConfig.java b/src/main/java/dev/mars3142/fhq/timezone_service/config/AppConfig.java new file mode 100644 index 0000000..da84b67 --- /dev/null +++ b/src/main/java/dev/mars3142/fhq/timezone_service/config/AppConfig.java @@ -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(); + } +} diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/exceptions/NotFoundException.java b/src/main/java/dev/mars3142/fhq/timezone_service/exceptions/NotFoundException.java new file mode 100644 index 0000000..7e22ffc --- /dev/null +++ b/src/main/java/dev/mars3142/fhq/timezone_service/exceptions/NotFoundException.java @@ -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 { + +} diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/TimeApiTimezoneZoneResponse.java b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/TimeApiTimezoneZoneResponse.java new file mode 100644 index 0000000..f4f4953 --- /dev/null +++ b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/TimeApiTimezoneZoneResponse.java @@ -0,0 +1,8 @@ +package dev.mars3142.fhq.timezone_service.timezone.domain.entities.response; + +public record TimeApiTimezoneZoneResponse(Interval dstInterval) { + + public record Interval(String dstName) { + + } +} diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/WorldTimeApiIpResponse.java b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/WorldTimeApiIpResponse.java new file mode 100644 index 0000000..22f42a5 --- /dev/null +++ b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/entities/response/WorldTimeApiIpResponse.java @@ -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) { + +} diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/LocationResponse.java b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/LocationResponse.java new file mode 100644 index 0000000..62c3696 --- /dev/null +++ b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/LocationResponse.java @@ -0,0 +1,7 @@ +package dev.mars3142.fhq.timezone_service.timezone.domain.model.response; + +import java.util.List; + +public record LocationResponse(int count, List locations) { + +} diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/TimeZoneResponse.java b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/TimeZoneResponse.java index 68177c7..a3671ee 100644 --- a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/TimeZoneResponse.java +++ b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/domain/model/response/TimeZoneResponse.java @@ -1,5 +1,5 @@ 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) { } diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/TimeZoneService.java b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/TimeZoneService.java index 3f63712..d0f9ea1 100644 --- a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/TimeZoneService.java +++ b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/TimeZoneService.java @@ -1,5 +1,16 @@ 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 { + + WorldTimeApiIpResponse getTimeZoneInfoByIp(); + + TimeApiTimezoneZoneResponse getTimeZoneInfo(String timezone); + String getPosixTimeZone(String timezone); + + List getLocations(String area); } diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/impl/TimeZoneServiceImpl.java b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/impl/TimeZoneServiceImpl.java index d46618d..e28eb49 100644 --- a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/impl/TimeZoneServiceImpl.java +++ b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/service/impl/TimeZoneServiceImpl.java @@ -1,18 +1,60 @@ 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 java.io.File; import java.io.IOException; import java.nio.file.Files; 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 org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; @Service +@RequiredArgsConstructor 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 public String getPosixTimeZone(String timezone) { val filename = Path.of("/usr/share/zoneinfo/" + timezone); + if (!filename.toFile().exists()) { + throw new NotFoundException(); + } try { val bytes = Files.readAllBytes(filename); val content = new String(bytes).split("\n"); @@ -21,4 +63,20 @@ public class TimeZoneServiceImpl implements TimeZoneService { throw new RuntimeException(e); } } + + @Override + public List 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(); + } } diff --git a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/web/controllers/TimeZoneController.java b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/web/controllers/TimeZoneController.java index 1f66520..95984fd 100644 --- a/src/main/java/dev/mars3142/fhq/timezone_service/timezone/web/controllers/TimeZoneController.java +++ b/src/main/java/dev/mars3142/fhq/timezone_service/timezone/web/controllers/TimeZoneController.java @@ -1,12 +1,12 @@ 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.service.TimeZoneService; import lombok.RequiredArgsConstructor; import lombok.val; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,9 +17,24 @@ public class TimeZoneController { private final TimeZoneService timeZoneService; - @PostMapping() - public TimeZoneResponse getTimeZone(@RequestBody TimeZoneRequest request) { - val timezone = timeZoneService.getPosixTimeZone(request.timezone()); - return new TimeZoneResponse(timezone); + @GetMapping + public TimeZoneResponse getTimeZone() { + val timezoneInfo = timeZoneService.getTimeZoneInfoByIp(); + 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); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index d7da77d..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=timezone-service diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..413cd7d --- /dev/null +++ b/src/main/resources/application.yaml @@ -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