1
0
Fork 0

Fix CORS
Build & Release / build-application (push) Successful in 7m41s Details
Build & Release / build-docker-image (push) Has been cancelled Details
Build & Release / deploy-to-production (push) Has been cancelled Details

This commit is contained in:
John Ahlroos 2025-02-16 20:08:09 +01:00
parent 4cab62f66b
commit 7f4bd1098e
Signed by: john
GPG Key ID: 258D0F70DB84CD5D
15 changed files with 86 additions and 78 deletions

View File

@ -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/') }}

View File

@ -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

View File

@ -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;

View File

@ -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<Map<String,String>> 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<String,Object> 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<GenerationResult> 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<Map<String,String>> 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<GenerationResult> 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 <T> HttpResponse<T> responseWithCorsHeaders(HttpRequest<?> httpRequest, MutableHttpResponse<T> response) {
return response
.header( "Access-Control-Allow-Origin", hostResolver.resolve(httpRequest))
.header("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
}
*/
}

View File

@ -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??
){}

View File

@ -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

View File

@ -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
){ }

View File

@ -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");

View File

@ -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")

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -6,7 +6,7 @@
</contextListener>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<withJansi>false</withJansi>
<encoder>
<pattern>%cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern>
</encoder>

View File

@ -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));
}
});

View File

@ -69,23 +69,7 @@
</ul>
</div>
<aside>
<div >
<h1 style="font-size: 2em">💬</h1>
<h4>Do you need to validate TIN numbers on your own site or in your organization?</h4>
</div>
We provide this same validation via a REST API as well!
</aside>
<footer style="position: fixed; bottom: 5px; right: 5px;"
_='init fetch "https://hits.devsoap.com/hit" as json with
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({
"host": document.location.host,
"ctx": window.location.pathname,
"url": window.location.href
})'>
<footer style="position: fixed; bottom: 5px; right: 5px;">
Service provided by <b><a href="https://devsoap.com" target="_blank" rel="noreferrer">devsoap.com</a></b>
</footer>
</body>

View File

@ -3,7 +3,6 @@ package com.devsoap.tincheck;
import com.devsoap.tincheck.services.TinCheckService;
import com.devsoap.tincheck.tin.TinCountry;
import com.devsoap.tincheck.tin.TinGenerator;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -17,7 +16,6 @@ import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Slf4j
public class TinCountryTest {
@ParameterizedTest(name = "{arguments}")
@ -29,7 +27,6 @@ public class TinCountryTest {
for (var i=0; i<50; i++) {
var date = LocalDate.of(random.nextInt(1854,2030),random.nextInt(1,12), random.nextInt(1,28));
var tin = service.generate(country.getCountryCode(), date, random.nextBoolean() ? TinGenerator.Gender.MALE: TinGenerator.Gender.FEMALE).value();
log.info("Generated TIN {} for country {}", tin, country.getCountryCode());
assertEquals("valid", service.validate(country.getCountryCode(), tin).result());
}
}