From 7f4bd1098e4bfdd81330dde3ad5a5696123828d0 Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Sun, 16 Feb 2025 20:08:09 +0100 Subject: [PATCH] Fix CORS --- .gitea/workflows/build.yaml | 6 +-- build.gradle | 15 ++++-- .../tincheck/ApplicationConfiguration.java | 4 ++ .../com/devsoap/tincheck/routes/Routes.java | 46 +++++++++---------- .../routes/requests/TinGenerateRequest.java | 8 ++-- .../routes/requests/TinValidateRequest.java | 5 +- .../routes/responses/GenerationResult.java | 9 ++-- .../routes/responses/ValidationResult.java | 4 +- .../tincheck/services/TinCheckService.java | 2 +- .../devsoap/tincheck/tin/TinGenerator.java | 21 +++++++-- src/main/resources/application.yml | 9 +--- src/main/resources/logback.xml | 2 +- .../public/htmx.org/extensions/json-enc.js | 12 +++++ src/main/resources/views/index.hbs | 18 +------- .../com/devsoap/tincheck/TinCountryTest.java | 3 -- 15 files changed, 86 insertions(+), 78 deletions(-) create mode 100644 src/main/resources/public/htmx.org/extensions/json-enc.js diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index e012901..105c930 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -30,7 +30,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' cache: 'gradle' - name: Cache Java dependencies uses: actions/cache@v3 @@ -46,12 +46,12 @@ jobs: - name: Run Unit tests run: ./gradlew --info --stacktrace test --fail-fast - name: Build Distribution - run: ./gradlew --info --stacktrace installDist + run: ./gradlew --info --stacktrace installOptimizedDist - name: Save distribution uses: actions/upload-artifact@v3 with: name: tincheck-${{env.TAG}} - path: build/install/tincheck + path: build/install/tincheck-optimized build-docker-image: if: ${{ needs.build-application.result == 'success' && startsWith(gitea.ref, 'refs/tags/') }} diff --git a/build.gradle b/build.gradle index 24887c6..74c4437 100644 --- a/build.gradle +++ b/build.gradle @@ -19,16 +19,18 @@ repositories { dependencies { annotationProcessor 'org.projectlombok:lombok' - testAnnotationProcessor 'org.projectlombok:lombok' + annotationProcessor 'io.micronaut.serde:micronaut-serde-processor' + annotationProcessor("io.micronaut:micronaut-http-validation") + compileOnly 'org.projectlombok:lombok' implementation(platform("io.micronaut.platform:micronaut-platform")) - implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'io.micronaut.views:micronaut-views-handlebars' + implementation 'io.micronaut.serde:micronaut-serde-jackson' + implementation 'io.micronaut.validation:micronaut-validation' - implementation 'jakarta.validation:jakarta.validation-api:3.1.1' - implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' + implementation("jakarta.annotation:jakarta.annotation-api") implementation "io.github.resilience4j:resilience4j-micronaut:$resilience4jVersion" implementation "io.github.resilience4j:resilience4j-ratelimiter:$resilience4jVersion" @@ -41,6 +43,7 @@ dependencies { runtimeOnly "org.webjars.npm:htmx.org:$htmxVersion" runtimeOnly "org.webjars.npm:hyperscript.org:$hyperscriptVersion" + runtimeOnly "org.yaml:snakeyaml" testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion" @@ -55,6 +58,10 @@ application { micronaut { version "$micronautVersion" runtime("netty") + processing { + incremental(true) + annotations("com.devsoap.tincheck.*") + } aot { cacheEnvironment = true optimizeServiceLoading = true diff --git a/src/main/java/com/devsoap/tincheck/ApplicationConfiguration.java b/src/main/java/com/devsoap/tincheck/ApplicationConfiguration.java index 35543ce..5669734 100644 --- a/src/main/java/com/devsoap/tincheck/ApplicationConfiguration.java +++ b/src/main/java/com/devsoap/tincheck/ApplicationConfiguration.java @@ -23,8 +23,12 @@ import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.helper.ConditionalHelpers; import com.github.jknack.handlebars.helper.EachHelper; import com.github.jknack.handlebars.helper.StringHelpers; +import io.micronaut.context.annotation.Bean; import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Replaces; +import io.micronaut.http.server.HttpServerConfiguration; +import io.micronaut.http.server.HttpServerConfiguration.CorsConfiguration; +import io.micronaut.http.server.cors.CorsFilter; import jakarta.inject.Singleton; import java.util.Random; diff --git a/src/main/java/com/devsoap/tincheck/routes/Routes.java b/src/main/java/com/devsoap/tincheck/routes/Routes.java index 9cfbef1..e3bc002 100644 --- a/src/main/java/com/devsoap/tincheck/routes/Routes.java +++ b/src/main/java/com/devsoap/tincheck/routes/Routes.java @@ -19,19 +19,16 @@ package com.devsoap.tincheck.routes; -import com.devsoap.tincheck.tin.TinGenerator; import com.devsoap.tincheck.routes.requests.TinGenerateRequest; import com.devsoap.tincheck.routes.requests.TinValidateRequest; import com.devsoap.tincheck.routes.responses.GenerationResult; import com.devsoap.tincheck.services.TinCheckService; +import com.devsoap.tincheck.tin.TinGenerator; import io.github.resilience4j.ratelimiter.RequestNotPermitted; -import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.MediaType; -import io.micronaut.http.MutableHttpResponse; -import io.micronaut.http.annotation.*; import io.micronaut.http.annotation.Error; -import io.micronaut.http.server.util.HttpHostResolver; +import io.micronaut.http.annotation.*; import io.micronaut.views.View; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -43,26 +40,13 @@ import java.util.Map; @Slf4j @Controller @RequiredArgsConstructor -@Consumes(MediaType.APPLICATION_FORM_URLENCODED) -@Produces(MediaType.TEXT_HTML) public class Routes { private final TinCheckService service; - private final HttpHostResolver hostResolver; - - @Post("/validate") - @View("validation-result") - public HttpResponse> validate(@Valid TinValidateRequest request, HttpRequest httpRequest) { - var result = service.validateRateLimited(request.country(), request.value()); - return responseWithCorsHeaders(httpRequest, HttpResponse.ok(switch (result.result()) { - case "valid" -> Map.of("class", result.result(), "text", "TIN is valid"); - case "invalid" -> Map.of("class", result.result(), "text", "TIN is invalid"); - default -> Map.of("class", result.result(), "text", "Unknown error"); - })); - } @Get @View("index") + @Produces(MediaType.TEXT_HTML) public Map index() { return Map.of( "countries", service.getCountryMap(), @@ -72,12 +56,21 @@ public class Routes { ); } - @Post("/generate") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public HttpResponse generate(@Valid TinGenerateRequest request, HttpRequest httpRequest){ - return responseWithCorsHeaders(httpRequest, HttpResponse.ok( - service.generateRateLimited(request.country(), request.dateOfBirth(), request.gender()))); + @Post("validate") + @View("validation-result") + @Produces(MediaType.TEXT_HTML) + public HttpResponse> validate(@Body @Valid TinValidateRequest request) { + var result = service.validateRateLimited(request.country(), request.value()); + return HttpResponse.ok(switch (result.result()) { + case "valid" -> Map.of("class", result.result(), "text", "TIN is valid"); + case "invalid" -> Map.of("class", result.result(), "text", "TIN is invalid"); + default -> Map.of("class", result.result(), "text", "Unknown error"); + }); + } + + @Post("generate") + public HttpResponse generate(@Body @Valid TinGenerateRequest request){ + return HttpResponse.ok(service.generateRateLimited(request.country(), request.dateOfBirth(), TinGenerator.Gender.fromSymbol(request.gender()))); } @Error(exception = IllegalArgumentException.class) @@ -93,9 +86,12 @@ public class Routes { "Free validations consumed. Please buy subscription for more validations.")); } + /* private HttpResponse responseWithCorsHeaders(HttpRequest httpRequest, MutableHttpResponse response) { return response .header( "Access-Control-Allow-Origin", hostResolver.resolve(httpRequest)) .header("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); } + + */ } \ No newline at end of file diff --git a/src/main/java/com/devsoap/tincheck/routes/requests/TinGenerateRequest.java b/src/main/java/com/devsoap/tincheck/routes/requests/TinGenerateRequest.java index f0a8f98..30993c9 100644 --- a/src/main/java/com/devsoap/tincheck/routes/requests/TinGenerateRequest.java +++ b/src/main/java/com/devsoap/tincheck/routes/requests/TinGenerateRequest.java @@ -22,15 +22,15 @@ package com.devsoap.tincheck.routes.requests; import com.devsoap.tincheck.tin.TinGenerator; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; -@Introspected +@Serdeable public record TinGenerateRequest( @JsonProperty(value = "country", required = true) @NotBlank String country, - @JsonProperty(value = "dateOfBirth", required = true) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") @NotNull LocalDate dateOfBirth, - @JsonProperty(value = "gender", required = true) @NotNull TinGenerator.Gender gender + @JsonProperty(value = "dateOfBirth", required = true) @JsonFormat(pattern = "yyyy-MM-dd") @NotNull LocalDate dateOfBirth, + @JsonProperty(value = "gender", required = true) @NotNull String gender // Using the enum here seems to break Serdeable?? ){} \ No newline at end of file diff --git a/src/main/java/com/devsoap/tincheck/routes/requests/TinValidateRequest.java b/src/main/java/com/devsoap/tincheck/routes/requests/TinValidateRequest.java index fd26129..85fa0ce 100644 --- a/src/main/java/com/devsoap/tincheck/routes/requests/TinValidateRequest.java +++ b/src/main/java/com/devsoap/tincheck/routes/requests/TinValidateRequest.java @@ -20,12 +20,11 @@ package com.devsoap.tincheck.routes.requests; import com.fasterxml.jackson.annotation.JsonProperty; -import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; - -@Introspected +@Serdeable public record TinValidateRequest( @JsonProperty("country") @NotNull String country, @JsonProperty("value") @NotBlank String value diff --git a/src/main/java/com/devsoap/tincheck/routes/responses/GenerationResult.java b/src/main/java/com/devsoap/tincheck/routes/responses/GenerationResult.java index 91fda83..15d965d 100644 --- a/src/main/java/com/devsoap/tincheck/routes/responses/GenerationResult.java +++ b/src/main/java/com/devsoap/tincheck/routes/responses/GenerationResult.java @@ -22,15 +22,14 @@ package com.devsoap.tincheck.routes.responses; import com.devsoap.tincheck.tin.TinGenerator; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; -import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; -@Introspected +@Serdeable public record GenerationResult( @JsonProperty("value") String value, - @JsonProperty("dateOfBirth") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") LocalDate dateOfBirth, - @JsonProperty("gender") @NotNull TinGenerator.Gender gender, - @JsonProperty("availableTokens") Long tokens + @JsonProperty("dateOfBirth") @JsonFormat(pattern = "yyyy-MM-dd") LocalDate dateOfBirth, + @JsonProperty("gender") @NotNull TinGenerator.Gender gender ){ } \ No newline at end of file diff --git a/src/main/java/com/devsoap/tincheck/routes/responses/ValidationResult.java b/src/main/java/com/devsoap/tincheck/routes/responses/ValidationResult.java index 4e9cccc..bbbcd67 100644 --- a/src/main/java/com/devsoap/tincheck/routes/responses/ValidationResult.java +++ b/src/main/java/com/devsoap/tincheck/routes/responses/ValidationResult.java @@ -20,9 +20,9 @@ package com.devsoap.tincheck.routes.responses; import com.fasterxml.jackson.annotation.JsonProperty; -import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; -@Introspected +@Serdeable public record ValidationResult(@JsonProperty("result") String result){ public static final ValidationResult VALID = new ValidationResult("valid"); public static final ValidationResult INVALID = new ValidationResult("invalid"); diff --git a/src/main/java/com/devsoap/tincheck/services/TinCheckService.java b/src/main/java/com/devsoap/tincheck/services/TinCheckService.java index 96408f7..c08ca08 100644 --- a/src/main/java/com/devsoap/tincheck/services/TinCheckService.java +++ b/src/main/java/com/devsoap/tincheck/services/TinCheckService.java @@ -60,7 +60,7 @@ public class TinCheckService { public GenerationResult generate(String countryCode, LocalDate dateOfBirth, TinGenerator.Gender gender) { var value = getByCountryCode(countryCode).generate(dateOfBirth, gender, randomGenerator); - return new GenerationResult(value, dateOfBirth, gender, 0L); + return new GenerationResult(value, dateOfBirth, gender); } @RateLimiter(name = "tincheck-public") diff --git a/src/main/java/com/devsoap/tincheck/tin/TinGenerator.java b/src/main/java/com/devsoap/tincheck/tin/TinGenerator.java index 40972db..4e12166 100644 --- a/src/main/java/com/devsoap/tincheck/tin/TinGenerator.java +++ b/src/main/java/com/devsoap/tincheck/tin/TinGenerator.java @@ -19,19 +19,34 @@ package com.devsoap.tincheck.tin; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +import io.micronaut.serde.annotation.Serdeable; import lombok.RequiredArgsConstructor; import java.time.LocalDate; +import java.util.Arrays; import java.util.Random; public interface TinGenerator { String generate(LocalDate yearOfBirth, Gender gender, Random randomGenerator); - @RequiredArgsConstructor + @Serdeable enum Gender { - MALE ("m"), FEMALE ("f"); - @JsonValue private final String symbol; + MALE ("m"), + FEMALE ("f"); + + Gender(String symbol) { + this.symbol = symbol; + } + + @JsonValue + private final String symbol; + + @JsonCreator + public static Gender fromSymbol(String symbol) { + return Arrays.stream(values()).filter(g -> g.symbol.equals(symbol)).findFirst().orElse(null); + } } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 757a28b..bf615bd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,8 @@ micronaut.application: name: tincheck micronaut.server: - cors.enabled: true + cors: + enabled: true micronaut.router: static-resources: @@ -12,12 +13,6 @@ micronaut.router: - "classpath:public" - "classpath:META-INF/resources/webjars" -jackson: - serialization: - writeDatesAsTimestamps: false - mapper: - acceptCaseInsensitiveEnums: true - resilience4j: ratelimiter: enabled: true diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 267d6c8..f052193 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -6,7 +6,7 @@ - true + false %cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) %magenta(%logger{36}) - %msg%n diff --git a/src/main/resources/public/htmx.org/extensions/json-enc.js b/src/main/resources/public/htmx.org/extensions/json-enc.js new file mode 100644 index 0000000..1446851 --- /dev/null +++ b/src/main/resources/public/htmx.org/extensions/json-enc.js @@ -0,0 +1,12 @@ +htmx.defineExtension('json-enc', { + onEvent: function (name, evt) { + if (name === "htmx:configRequest") { + evt.detail.headers['Content-Type'] = "application/json"; + } + }, + + encodeParameters : function(xhr, parameters, elt) { + xhr.overrideMimeType('text/json'); + return (JSON.stringify(parameters)); + } +}); \ No newline at end of file diff --git a/src/main/resources/views/index.hbs b/src/main/resources/views/index.hbs index 1190288..0921ce6 100644 --- a/src/main/resources/views/index.hbs +++ b/src/main/resources/views/index.hbs @@ -69,23 +69,7 @@ - - -