diff --git a/.dockerignore b/.dockerignore index f3d88a8b..e69de29b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +0,0 @@ -* -!target/*-runner -!target/*-runner.jar -!target/lib/* -!target/quarkus-app/* diff --git a/.gitignore b/.gitignore index bdf57ce3..c702f62a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,28 @@ nb-configuration.xml # Local environment .env + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json + +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +/tools/api/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4c5d2036 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM eclipse-temurin:17-jdk + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' +WORKDIR /app + +COPY entrypoint.sh ./ +COPY --chown=185 service/target/quarkus-app/lib /app/lib +COPY --chown=185 service/target/quarkus-app/app /app/app +COPY --chown=185 service/target/quarkus-app/quarkus /app/quarkus +COPY --chown=185 service/target/quarkus-app/quarkus-run.jar /app/app.jar + +EXPOSE 8080 +USER 185 + +CMD ["/app/entrypoint.sh"] diff --git a/Makefile b/Makefile index 8ef162ab..c0c94631 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ .EXPORT_ALL_VARIABLES: PROJECT_VERSION=0.0.0-SNAPSHOT +dev-dependencies: + docker compose up -d database + build-all: build-openapi mvn package -Pprod @@ -13,8 +16,10 @@ build: build-openapi: mvn package -Popen-api -DskipTests -DskipChecks -am -pl "service" +build-all:build-openapi web-build build-fast dev-dependencies + run: - java -jar service/target/quarkus-app/quarkus-run.jar + docker compose up -d service --build dev-service: mvn compile quarkus:dev -am -pl "service" @@ -24,7 +29,8 @@ dev:dev-service web-build: mvn clean package -pl web -DskipTests -web-dev: - yarn start --prefix ./web - +dev-web: + yarn --cwd ./web dev +generate-jwt: + mvn exec:java -pl "service" -Dexec.mainClass=com.lopixlabs.app.fullstackdemo.GenerateToken -Dexec.classpathScope=test -Dsmallrye.jwt.sign.key.location=privateKey.pem diff --git a/README.md b/README.md index 1fec3d98..e15a3d1a 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ * [Open-api generate](https://openapi-generator.tech/docs/generators/typescript-axios) ## Frontend -* [React](https://reactjs.org/) +* [svelte](https://svelte.dev) * [Vite](https://vitejs.dev/) -* [mantine](https://mantine.dev/) +* [stwui](https://stwui.vercel.app/) ## How to run project diff --git a/doc/pom.xml b/doc/pom.xml index c2eb67f8..1f4b646d 100644 --- a/doc/pom.xml +++ b/doc/pom.xml @@ -41,6 +41,13 @@ + + + org.asciidoctor + asciidoctorj-diagram + 2.2.1 + + diff --git a/doc/src/main/asciidoc/auth.adoc b/doc/src/main/asciidoc/auth.adoc new file mode 100644 index 00000000..df311738 --- /dev/null +++ b/doc/src/main/asciidoc/auth.adoc @@ -0,0 +1,7 @@ + +The diagram shows flow of how we implemented Authentication process with Access Token and Refresh Token. + +["plantuml","containers",svg] +plantuml::auth.puml[] + + diff --git a/doc/src/main/asciidoc/auth.puml b/doc/src/main/asciidoc/auth.puml new file mode 100644 index 00000000..5d21a087 --- /dev/null +++ b/doc/src/main/asciidoc/auth.puml @@ -0,0 +1,27 @@ +@startuml +autonumber 1 + +alt User Login +Client -> Server: Login \nPOST /api/auth/signin {username, password} +activate Server +Server --> Client: Authentication Response {token, refreshToken} +deactivate Server +end alt + +alt Access resource with Expired token +Client -> Server: Request data with JWT on header +activate Server +Server --> Client: Token Expired +deactivate Server +end alt + +alt Refresh token +Client -> Server: Send Refresh token request \nPOST /api/auth/refreshToken +activate Server +Server --> Client: Authentication Response {token, refreshToken} +deactivate Server +end alt + + + +@enduml diff --git a/doc/src/main/asciidoc/jwt.adoc b/doc/src/main/asciidoc/jwt.adoc new file mode 100644 index 00000000..8fe1f56b --- /dev/null +++ b/doc/src/main/asciidoc/jwt.adoc @@ -0,0 +1,14 @@ + +|=== +|Property |Description +|iss|The issuer of the token, typically the server that generated it. +|sub|The subject of the token, typically the user to whom the token pertains. +|aud|The intended audience of the token, typically the server(s) that should accept and validate the token. +|exp|The expiration time of the token, after which it should no longer be accepted. +|iat|The time at which the token was issued. +|jti|The unique identifier of the token, which can be used to prevent replay attacks. + +|=== + + + diff --git a/doc/src/main/asciidoc/openapi/.openapi-generator/openapi.yaml-asciidoc.sha256 b/doc/src/main/asciidoc/openapi/.openapi-generator/openapi.yaml-asciidoc.sha256 index 463b9162..23e927b9 100644 --- a/doc/src/main/asciidoc/openapi/.openapi-generator/openapi.yaml-asciidoc.sha256 +++ b/doc/src/main/asciidoc/openapi/.openapi-generator/openapi.yaml-asciidoc.sha256 @@ -1 +1 @@ -d624c50784935cfcca1dc30c29b4b047623e40011cd9bf82ec19db74c9422e24 \ No newline at end of file +6d5f8a8d60ea444cdfd2e044ca2a9febf4859bda239197eb81d54290bcfc9a2e \ No newline at end of file diff --git a/doc/src/main/asciidoc/openapi/index.adoc b/doc/src/main/asciidoc/openapi/index.adoc index 95c8d842..f4b25d0d 100644 --- a/doc/src/main/asciidoc/openapi/index.adoc +++ b/doc/src/main/asciidoc/openapi/index.adoc @@ -1,16 +1,16 @@ -= service API += Quarkus and svelte demo application team@openapitools.org -1.0.0-SNAPSHOT +1.0.0 :toc: left :numbered: :toclevels: 3 :source-highlighter: highlightjs -:keywords: openapi, rest, service API +:keywords: openapi, rest, Quarkus and svelte demo application :specDir: :snippetDir: :generator-template: v1 2019-12-20 :info-url: https://openapi-generator.tech -:app-name: service API +:app-name: Quarkus and svelte demo application [abstract] .Abstract @@ -24,14 +24,248 @@ No description provided (generated by Openapi Generator https://github.com/opena == Endpoints +[.AuthResource] +=== AuthResource + + +[.apiAuthRefreshGet] +==== apiAuthRefreshGet + +`GET /api/auth/refresh` + + + +===== Description + + + + +// markup not found, no include::{specDir}api/auth/refresh/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.http response codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| OK +| <> + + +| 401 +| Not Authorized +| <<>> + + +| 403 +| Not Allowed +| <<>> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}api/auth/refresh/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}api/auth/refresh/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :api/auth/refresh/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}api/auth/refresh/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.apiAuthSigninPost] +==== apiAuthSigninPost + +`POST /api/auth/signin` + + + +===== Description + + + + +// markup not found, no include::{specDir}api/auth/signin/POST/spec.adoc[opts=optional] + + + +===== Parameters + + +====== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| Login +| <> +| - +| +| + +|=== + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.http response codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| OK +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}api/auth/signin/POST/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}api/auth/signin/POST/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :api/auth/signin/POST/POST.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}api/auth/signin/POST/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + [.GreetingResource] === GreetingResource -[.helloGet] -==== helloGet +[.helloPermitAllGet] +==== helloPermitAllGet + +`GET /hello/permit-all` + + + +===== Description + + + + +// markup not found, no include::{specDir}hello/permit-all/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.http response codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| OK +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}hello/permit-all/GET/http-request.adoc[opts=optional] -`GET /hello` + +// markup not found, no include::{snippetDir}hello/permit-all/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :hello/permit-all/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}hello/permit-all/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.helloRolesAllowedGet] +==== helloRolesAllowedGet + +`GET /hello/roles-allowed` @@ -40,7 +274,7 @@ No description provided (generated by Openapi Generator https://github.com/opena -// markup not found, no include::{specDir}hello/GET/spec.adoc[opts=optional] +// markup not found, no include::{specDir}hello/roles-allowed/GET/spec.adoc[opts=optional] @@ -73,25 +307,35 @@ No description provided (generated by Openapi Generator https://github.com/opena | OK | <> + +| 401 +| Not Authorized +| <<>> + + +| 403 +| Not Allowed +| <<>> + |=== ===== Samples -// markup not found, no include::{snippetDir}hello/GET/http-request.adoc[opts=optional] +// markup not found, no include::{snippetDir}hello/roles-allowed/GET/http-request.adoc[opts=optional] -// markup not found, no include::{snippetDir}hello/GET/http-response.adoc[opts=optional] +// markup not found, no include::{snippetDir}hello/roles-allowed/GET/http-response.adoc[opts=optional] -// file not found, no * wiremock data link :hello/GET/GET.json[] +// file not found, no * wiremock data link :hello/roles-allowed/GET/GET.json[] ifdef::internal-generation[] ===== Implementation -// markup not found, no include::{specDir}hello/GET/implementation.adoc[opts=optional] +// markup not found, no include::{specDir}hello/roles-allowed/GET/implementation.adoc[opts=optional] endif::internal-generation[] @@ -101,6 +345,31 @@ endif::internal-generation[] == Models +[#AuthResult] +=== _AuthResult_ + + + +[.fields-AuthResult] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| token +| +| String +| +| + +| refreshToken +| +| String +| +| + +|=== + + [#Hello] === _Hello_ @@ -120,3 +389,28 @@ endif::internal-generation[] |=== +[#Login] +=== _Login_ + + + +[.fields-Login] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| username +| +| String +| +| + +| password +| +| String +| +| + +|=== + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9fd36757 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3' + + + +services: + service: + build: + context: . + dockerfile: Dockerfile + image: fullstackdemo + restart: on-failure + ports: + - "8081:8080" + environment: + - QUARKUS_DATASOURCE_USERNAME=user + - QUARKUS_DATASOURCE_PASSWORD=password + - QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://database:5432/db + depends_on: + - database + + database: + image: postgres:14.7 + hostname: database + restart: always + ports: + - "5432:5432" + environment: + - ALLOW_IP_RANGE='0.0.0.0/0' + - POSTGRES_DB=db + - POSTGRES_USER=user + - POSTGRES_PASSWORD=password + diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000..61d5ce3f --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +exec java $JVM_ARGS -jar /app/app.jar + diff --git a/pom.xml b/pom.xml index 84c4f011..07272807 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 3.11.0 false - 11 + 17 UTF-8 UTF-8 quarkus-bom diff --git a/service/pom.xml b/service/pom.xml index 8596e2ac..8e1fa377 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -23,6 +23,26 @@ io.quarkus quarkus-smallrye-openapi + + io.quarkus + quarkus-security + + + io.quarkus + quarkus-smallrye-jwt + + + io.quarkus + quarkus-smallrye-jwt-build + + + io.quarkus + quarkus-hibernate-orm-panache + + + io.quarkus + quarkus-jdbc-postgresql + org.projectlombok lombok @@ -33,11 +53,31 @@ quarkus-junit5 test + + io.quarkus + quarkus-test-security-jwt + test + io.rest-assured rest-assured test + + io.quarkus + quarkus-test-security + test + + + io.quarkus.resteasy.reactive + resteasy-reactive-common + 2.16.4.Final + + + io.quarkus + quarkus-security-jpa + 2.16.4.Final + diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/App.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/App.java new file mode 100644 index 00000000..c4ac926b --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/App.java @@ -0,0 +1,31 @@ +package com.lopixlabs.app.fullstackdemo; + + +import javax.ws.rs.core.Application; +import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition; +import org.eclipse.microprofile.openapi.annotations.enums.SecuritySchemeType; +import org.eclipse.microprofile.openapi.annotations.info.Info; +import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; +import org.eclipse.microprofile.openapi.annotations.security.SecurityScheme; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +@OpenAPIDefinition( + tags = { + @Tag(name = "Demo", description = "Quarkus and svelte demo application"), + }, + info = @Info( + title = "Quarkus and svelte demo application", + version = "1.0.0" + ), + security = @SecurityRequirement(name = "basicAuth") +) +//@SecurityScheme( +// securitySchemeName = "basicAuth", // can be set to anything +// type = SecuritySchemeType.HTTP, +// scheme = "basic" +//) +@SecurityScheme( + type = SecuritySchemeType.APIKEY +) +public class App extends Application { +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/GreetingResource.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/GreetingResource.java deleted file mode 100644 index a0762875..00000000 --- a/service/src/main/java/com/lopixlabs/app/fullstackdemo/GreetingResource.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.lopixlabs.app.fullstackdemo; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; - -@Path("/hello") -@Produces(MediaType.APPLICATION_JSON) -public class GreetingResource { - - @GET - public Hello hello() { - return Hello.of("hello from quarkus!!!"); - } -} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/Hello.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/Hello.java deleted file mode 100644 index b5ea4608..00000000 --- a/service/src/main/java/com/lopixlabs/app/fullstackdemo/Hello.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.lopixlabs.app.fullstackdemo; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class Hello { - String value; -} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/AuthResource.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/AuthResource.java new file mode 100644 index 00000000..d387e1c8 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/AuthResource.java @@ -0,0 +1,46 @@ +package com.lopixlabs.app.fullstackdemo.auth; + +import static com.lopixlabs.app.fullstackdemo.security.Roles.REFRESH; + +import com.lopixlabs.app.fullstackdemo.auth.model.Login; +import com.lopixlabs.app.fullstackdemo.auth.model.AuthResult; +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import org.eclipse.microprofile.jwt.JsonWebToken; + +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@ApplicationScoped +@Path("/api/auth") +public class AuthResource { + private final AuthService authService; + private final JsonWebToken jwt; + + @Inject + public AuthResource(final AuthService authService, final JsonWebToken jwt) { + this.authService = authService; + this.jwt = jwt; + } + + @POST + @Path("signin") + @PermitAll + public AuthResult login(final Login login) { + return authService.login(login); + } + + @GET + @Path("refresh") + @RolesAllowed(REFRESH) + public AuthResult refresh() { + return authService.refresh(jwt); + } +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/AuthService.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/AuthService.java new file mode 100644 index 00000000..370b4cfe --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/AuthService.java @@ -0,0 +1,35 @@ +package com.lopixlabs.app.fullstackdemo.auth; + +import static com.lopixlabs.app.fullstackdemo.users.UserRole.USER; + +import com.lopixlabs.app.fullstackdemo.auth.model.Login; +import com.lopixlabs.app.fullstackdemo.auth.model.AuthResult; +import com.lopixlabs.app.fullstackdemo.security.TokenService; +import java.util.List; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import org.eclipse.microprofile.jwt.JsonWebToken; + +@ApplicationScoped +public class AuthService { + private final TokenService tokenService; + + @Inject + public AuthService(final TokenService tokenService) { + this.tokenService = tokenService; + } + + public AuthResult login(final Login login) { + //final String authentication = new String(Base64.decode(authorization)); //user:passs + + final String token = tokenService.generate("teste@test.com", List.of(USER.getRoleName())); + final String refreshToken = tokenService.generateRefreshToken("teste@test.com"); + return AuthResult.of(token, refreshToken); + } + + public AuthResult refresh(final JsonWebToken jwt) { + final String token = tokenService.generate("teste@test.com", List.of(USER.getRoleName())); + final String refreshToken = tokenService.generateRefreshToken("teste@test.com"); + return AuthResult.of(token, refreshToken); + } +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/model/AuthResult.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/model/AuthResult.java new file mode 100644 index 00000000..e0da0cf1 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/model/AuthResult.java @@ -0,0 +1,13 @@ +package com.lopixlabs.app.fullstackdemo.auth.model; + +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@Value +@AllArgsConstructor(staticName = "of") +@Jacksonized +public class AuthResult { + String token; + String refreshToken; +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/model/Login.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/model/Login.java new file mode 100644 index 00000000..f02c7fd2 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/auth/model/Login.java @@ -0,0 +1,15 @@ +package com.lopixlabs.app.fullstackdemo.auth.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@Value +@AllArgsConstructor +@Builder +@Jacksonized +public class Login { + String username; + String password; +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/commom/NotFoundExceptionMapper.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/commom/NotFoundExceptionMapper.java new file mode 100644 index 00000000..142ac36d --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/commom/NotFoundExceptionMapper.java @@ -0,0 +1,23 @@ +package com.lopixlabs.app.fullstackdemo.commom; + +import static org.jboss.resteasy.reactive.RestResponse.StatusCode.NOT_FOUND; + +import java.io.InputStream; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class NotFoundExceptionMapper implements ExceptionMapper { + @Override + @Produces(MediaType.TEXT_HTML) + public Response toResponse(NotFoundException exception) { + InputStream resource = ClassLoader.getSystemResourceAsStream("META-INF/resources/index.html"); + return null == resource + ? Response.status(NOT_FOUND).build() + : Response.ok().entity(resource).build(); + } +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/hello/GreetingResource.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/hello/GreetingResource.java new file mode 100644 index 00000000..017048bd --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/hello/GreetingResource.java @@ -0,0 +1,55 @@ +package com.lopixlabs.app.fullstackdemo.hello; + +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; +import org.eclipse.microprofile.jwt.JsonWebToken; + +@Path("/hello") +@Produces(MediaType.APPLICATION_JSON) +public class GreetingResource { + @Inject + JsonWebToken jwt; + + @GET + @Path("permit-all") + @PermitAll + public Hello hello(@Context SecurityContext ctx) { + return Hello.of(getResponseString(ctx)); + } + @GET + @Path("roles-allowed") + @RolesAllowed({ "user", "admin" }) + public Hello helloRolesAllowed(@Context SecurityContext ctx) { + return Hello.of(getResponseString(ctx)); + } + + + private String getResponseString(SecurityContext ctx) { + String name; + if (ctx.getUserPrincipal() == null) { + name = "anonymous"; + } else if (!ctx.getUserPrincipal().getName().equals(jwt.getName())) { + throw new InternalServerErrorException("Principal and JsonWebToken names do not match"); + } else { + name = ctx.getUserPrincipal().getName(); + } + return String.format("hello + %s," + + " isHttps: %s," + + " authScheme: %s," + + " hasJWT: %s", + name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJwt()); + } + + private boolean hasJwt() { + return jwt.getClaimNames() != null; + } + +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/hello/Hello.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/hello/Hello.java new file mode 100644 index 00000000..37c1df4d --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/hello/Hello.java @@ -0,0 +1,10 @@ +package com.lopixlabs.app.fullstackdemo.hello; + +import lombok.AllArgsConstructor; +import lombok.Value; + +@Value +@AllArgsConstructor(staticName = "of") +public class Hello { + String value; +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/Roles.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/Roles.java new file mode 100644 index 00000000..b7c32d15 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/Roles.java @@ -0,0 +1,7 @@ +package com.lopixlabs.app.fullstackdemo.security; + +public class Roles { + public static final String REFRESH = "refresh"; + public static final String USER = "user"; + public static final String ADMIN = "admin"; +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenException.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenException.java new file mode 100644 index 00000000..30d0f7f1 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenException.java @@ -0,0 +1,6 @@ +package com.lopixlabs.app.fullstackdemo.security; + +public class TokenException extends RuntimeException { + public TokenException(final String s, final Exception e) { + } +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenService.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenService.java new file mode 100644 index 00000000..125edb1c --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenService.java @@ -0,0 +1,44 @@ +package com.lopixlabs.app.fullstackdemo.security; + +import static com.lopixlabs.app.fullstackdemo.security.Roles.REFRESH; + +import io.smallrye.jwt.build.Jwt; +import java.time.Instant; +import java.util.HashSet; +import java.util.List; +import javax.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; + +@ApplicationScoped +@Slf4j +public class TokenService { + private final TokenSettings tokenSettings; + + // TODO WIP + public TokenService(final TokenSettings tokenSettings) { + this.tokenSettings = tokenSettings; + } + + public String generate(final String email, final List roles) { + try { + return Jwt.issuer(tokenSettings.getIssuer()) + .upn(email) + .issuedAt(Instant.now()) + .expiresAt(Instant.now().plusSeconds(tokenSettings.getExpiresSeconds())) + .groups(new HashSet<>(roles)) + .sign(); + } catch (Exception e) { + log.error("Error generate token: " + e.getMessage(), e); + throw new TokenException("Error generate token: " + e.getMessage(), e); + } + } + + public String generateRefreshToken(String email) { + return Jwt.issuer(tokenSettings.getIssuer()) + .upn(email) + .issuedAt(Instant.now()) + .expiresAt(Instant.now().plusSeconds(tokenSettings.getRefreshExpiresSeconds())) + .groups(new HashSet<>(List.of(REFRESH))) + .sign(); + } +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenSettings.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenSettings.java new file mode 100644 index 00000000..34fb4359 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/security/TokenSettings.java @@ -0,0 +1,25 @@ +package com.lopixlabs.app.fullstackdemo.security; + +import javax.enterprise.context.ApplicationScoped; +import lombok.Getter; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@ApplicationScoped +@Getter +public class TokenSettings { + public static final String EXPIRE_5MIN = "300"; + public static final String EXPIRE_24HOURS = "86400"; + private final String issuer; + private final int expiresSeconds; + private final int refreshExpiresSeconds; + + public TokenSettings( + @ConfigProperty(name = "mp.jwt.verify.issuer") final String issuer, + @ConfigProperty(name = "jwt.expires", defaultValue = EXPIRE_5MIN) final int expiresSeconds, + @ConfigProperty(name = "jwt.refreshExpires", defaultValue = EXPIRE_24HOURS) final int refreshExpiresSeconds + ) { + this.issuer = issuer; + this.expiresSeconds = expiresSeconds; + this.refreshExpiresSeconds = refreshExpiresSeconds; + } +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/Role.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/Role.java new file mode 100644 index 00000000..516b3e26 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/Role.java @@ -0,0 +1,42 @@ +package com.lopixlabs.app.fullstackdemo.users; + +import static lombok.AccessLevel.PRIVATE; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import java.time.LocalDateTime; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.GenericGenerator; + +@Data +@NoArgsConstructor() +@AllArgsConstructor(access = PRIVATE) +@Accessors(chain = true) +@EqualsAndHashCode(of = {"id"}) +@Builder(toBuilder = true) +@Entity(name = "_role") +public class Role extends PanacheEntityBase { + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid") + @Column(name = "id", nullable = false, updatable = false) + @Setter(AccessLevel.PROTECTED) + private String id; + + @CreationTimestamp + private LocalDateTime createAt = LocalDateTime.now(); + private String role; +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/User.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/User.java new file mode 100644 index 00000000..0e95ecc8 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/User.java @@ -0,0 +1,70 @@ +package com.lopixlabs.app.fullstackdemo.users; + +import static lombok.AccessLevel.PRIVATE; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.quarkus.elytron.security.common.BcryptUtil; +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.security.jpa.Roles; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.GenericGenerator; + +@Data +@NoArgsConstructor() +@AllArgsConstructor(access = PRIVATE) +@Accessors(chain = true) +@EqualsAndHashCode(of = {"id"}) +@Builder(toBuilder = true) +@Entity(name = "_user") +public class User extends PanacheEntityBase { + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid") + @Column(name = "id", nullable = false, updatable = false) + @Setter(AccessLevel.PROTECTED) + private String id; + @NotNull + private String username; + + @CreationTimestamp + private LocalDateTime createAt = LocalDateTime.now(); + @Email + private String email; + + private Boolean enabled = true; + + private String password; + + @ManyToMany + public List roles = new ArrayList<>(); + + public static void add(String username, String password, List role) { + User user = new User(); + user.username = username; + user.password = BcryptUtil.bcryptHash(password); + user.roles = role; + user.persist(); + } +} diff --git a/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/UserRole.java b/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/UserRole.java new file mode 100644 index 00000000..94d22104 --- /dev/null +++ b/service/src/main/java/com/lopixlabs/app/fullstackdemo/users/UserRole.java @@ -0,0 +1,16 @@ +package com.lopixlabs.app.fullstackdemo.users; + +import com.lopixlabs.app.fullstackdemo.security.Roles; +import lombok.Getter; + +public enum UserRole { + USER(Roles.USER), + ADMIN(Roles.ADMIN); + + @Getter + private final String roleName; + + UserRole(final String roleName) { + this.roleName = roleName; + } +} diff --git a/service/src/main/resources/api/openapi.json b/service/src/main/resources/api/openapi.json index 616ba767..1a413ccd 100644 --- a/service/src/main/resources/api/openapi.json +++ b/service/src/main/resources/api/openapi.json @@ -1,8 +1,8 @@ { "openapi" : "3.0.3", "info" : { - "title" : "service API", - "version" : "1.0.0-SNAPSHOT" + "title" : "Quarkus and svelte demo application", + "version" : "1.0.0" }, "servers" : [ { "url" : "http://localhost:8080", @@ -11,8 +11,69 @@ "url" : "http://0.0.0.0:8080", "description" : "Auto generated value" } ], + "security" : [ { + "basicAuth" : [ ] + } ], + "tags" : [ { + "name" : "Demo", + "description" : "Quarkus and svelte demo application" + } ], "paths" : { - "/hello" : { + "/api/auth/refresh" : { + "get" : { + "tags" : [ "Auth Resource" ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AuthResult" + } + } + } + }, + "401" : { + "description" : "Not Authorized" + }, + "403" : { + "description" : "Not Allowed" + } + }, + "security" : [ { + "SecurityScheme" : [ "refresh" ] + }, { + "SecurityScheme" : [ "refresh" ] + } ] + } + }, + "/api/auth/signin" : { + "post" : { + "tags" : [ "Auth Resource" ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Login" + } + } + } + }, + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AuthResult" + } + } + } + } + } + } + }, + "/hello/permit-all" : { "get" : { "tags" : [ "Greeting Resource" ], "responses" : { @@ -28,10 +89,49 @@ } } } + }, + "/hello/roles-allowed" : { + "get" : { + "tags" : [ "Greeting Resource" ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Hello" + } + } + } + }, + "401" : { + "description" : "Not Authorized" + }, + "403" : { + "description" : "Not Allowed" + } + }, + "security" : [ { + "SecurityScheme" : [ "user", "admin" ] + }, { + "SecurityScheme" : [ "user", "admin" ] + } ] + } } }, "components" : { "schemas" : { + "AuthResult" : { + "type" : "object", + "properties" : { + "token" : { + "type" : "string" + }, + "refreshToken" : { + "type" : "string" + } + } + }, "Hello" : { "type" : "object", "properties" : { @@ -39,6 +139,17 @@ "type" : "string" } } + }, + "Login" : { + "type" : "object", + "properties" : { + "username" : { + "type" : "string" + }, + "password" : { + "type" : "string" + } + } } } } diff --git a/service/src/main/resources/api/openapi.yaml b/service/src/main/resources/api/openapi.yaml index 4e73d310..d0485d8d 100644 --- a/service/src/main/resources/api/openapi.yaml +++ b/service/src/main/resources/api/openapi.yaml @@ -1,15 +1,56 @@ --- openapi: 3.0.3 info: - title: service API - version: 1.0.0-SNAPSHOT + title: Quarkus and svelte demo application + version: 1.0.0 servers: - url: http://localhost:8080 description: Auto generated value - url: http://0.0.0.0:8080 description: Auto generated value +security: +- basicAuth: [] +tags: +- name: Demo + description: Quarkus and svelte demo application paths: - /hello: + /api/auth/refresh: + get: + tags: + - Auth Resource + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AuthResult' + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: + - refresh + - SecurityScheme: + - refresh + /api/auth/signin: + post: + tags: + - Auth Resource + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Login' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AuthResult' + /hello/permit-all: get: tags: - Greeting Resource @@ -20,10 +61,46 @@ paths: application/json: schema: $ref: '#/components/schemas/Hello' + /hello/roles-allowed: + get: + tags: + - Greeting Resource + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Hello' + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: + - user + - admin + - SecurityScheme: + - user + - admin components: schemas: + AuthResult: + type: object + properties: + token: + type: string + refreshToken: + type: string Hello: type: object properties: value: type: string + Login: + type: object + properties: + username: + type: string + password: + type: string diff --git a/service/src/main/resources/application.properties b/service/src/main/resources/application.properties index 5c3c0b0d..0fdb9901 100644 --- a/service/src/main/resources/application.properties +++ b/service/src/main/resources/application.properties @@ -1,4 +1,29 @@ quarkus.smallrye-openapi.store-schema-directory=src/main/resources/api %dev.quarkus.http.cors=true -quarkus.http.access-log.enabled=true +%dev.quarkus.http.cors.origins=* + +%dev.quarkus.http.access-log.enabled=true +%dev.quarkus.log.category."io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator".level=TRACE +%dev.quarkus.log.category."io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator".min-level=TRACE + +mp.jwt.verify.publickey.location=publicKey.pem +smallrye.jwt.sign.key.location=privateKey.pem +mp.jwt.verify.issuer=https://localhost/issuer + +%dev.quarkus.hibernate-orm.database.generation=drop-and-create + +quarkus.native.resources.includes=publicKey.pem +%dev.jwt.expires=1 +#DB +quarkus.datasource.db-kind=postgresql +quarkus.hibernate-orm.database.globally-quoted-identifiers=true +quarkus.hibernate-orm.metrics.enabled=true +quarkus.hibernate-orm.jdbc.timezone=UTC +quarkus.hibernate-orm.statistics=true +quarkus.hibernate-orm.log-session-metrics=true +quarkus.datasource.username=user +quarkus.datasource.password=password +quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/db +quarkus.hibernate-orm.log.sql=true + diff --git a/service/src/main/resources/privateKey.pem b/service/src/main/resources/privateKey.pem new file mode 100644 index 00000000..19dbe61c --- /dev/null +++ b/service/src/main/resources/privateKey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYnJ3r2stJLdEo +IV3e3C+qjE91yHVhKAQ+0eJdSTPUzbhsrRPCjV6TGHi5S5jkNj4um0SVI6scV0aK +lwpeewFssK26X8Be/afuveGEyhkFNkbOdfjI9/Oug7Zl/fZXl21+CYH2SWxXXwx9 +DGcPcR6gB3jwsB2BqVKnywbTflbzhrt7WToc3uj0F20Q4j2qsPDtRjIXAsICqsdb +IQIPKaVaqBDf+GyFIS+3lrbplbVo6aHo3OMfP8KiDKnKK/Pq1yPanMxZHBjUBk0S +PxLijlKn7DA11SpdGkp5S97DdbWceAcMflvM0KwONd/N5s3Ai9fhdPZa5uO++l2T +b9ey0P5dAgMBAAECggEAbkzyLZOwSIdIJkM3ZbCBpTrli3iULPEPmF2QAyiveAF5 +k/1ePod2TTj84H4aULs+VsVB2x/algfrJ89ZvFqukwY0sN7pOnW3+d4+5Dt1Gi5V +R/TSxqNcNtVjH/R+aIP3gWKeByfEP4lq9pJGOJrFmX+0mftUSx5JtJ+xzihbZo2Z +JCUOOMG71yk6Zx1dWUQi6eUwHAKF1lxbSZHmtdAKU1UIOm+eXVl1AvdiGxyFGY/i +mqEyF1afBX4WFSY0Myk3p82XJLjPclqOL+2jQ9nP696NOg17r3doM1vjbyWV6O4n +cSHUYigV2H4HOo/o3W6hmH4XHKTtf2nSQCPGgdSSAQKBgQDymkWfRaDSl/gGphLV +TXfPXdkRMSafP3ygYVwsGCkTz1mAjk0XgnFGGGO1zd8eJtrZK763+hA8q9kdQRiP +93uPUJNSZfg71lpftNqQl58Blsl4kMFE6by8WjxaEi86lFoyRZMHCup9QJEbVk6i +ZsnaDMFTgqOKuTpJaqkNx+zSIQKBgQDkkugTgjdkwGv58foOMXSwKnFwi6Cnveqa +bSIVZEdq8rAFUp8k66zbUdc/7ke5+hCIKNJXgsSCN6SIPtkUZ/pxRmYmvyFgY1BM +psqClpBJ72zXrLpgZVGm9Fjk3WREXQzr05UI77UWd1RQqOFTSpqCFl5l2phECB5E +YU/1WrZcvQKBgQCL1oDrHadlvFMrAe85JD47OU01q68ne+DKUVRf1k1XLd3ZTC0D +sBy62rQex1wkdSEQrTZxtaJGVof/ZNZTpcODCF7i0AQV+ah+IjEZx0vQDqpy5H2C +y3gO2bSP9otiqMCU2aW2GYSwTddWepk44ybWmSOWj2bVP49pYiGv392SAQKBgQDB +ynvQ7noghbQGOc4T8oJISfPbw8X18eLSAuz+zgJu/kuWd7JODzKC0CR7I37Ugts5 +VcLgRxJshNiOWxqZXn7LQzCgcTPPEOSn2FGh3h1/UesqAFwUFlLgBhLIF5xHCc1G +tCCLbxBNd+aYbFZ906aj3Gs0V9z48RkYSJkOIFa00QKBgDdYMrUXn1N2WsToJ1W/ +f2UO7qwQNyT9RD8WAGSBCWyEYxok5dTbXezI88QYPWQna1qPn2daHPbKV3O3K82l +rzV1/sXD1LQu+cse1Tv85H99AL4CJyeUugWTiaVU+BvavCoTNDjKOf8W7fzGlz1g +n1qeDefY4o5AqUpeBizG2kvV +-----END PRIVATE KEY----- diff --git a/service/src/main/resources/publicKey.pem b/service/src/main/resources/publicKey.pem new file mode 100644 index 00000000..7ea50bf9 --- /dev/null +++ b/service/src/main/resources/publicKey.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Jyd69rLSS3RKCFd3twv +qoxPdch1YSgEPtHiXUkz1M24bK0Two1ekxh4uUuY5DY+LptElSOrHFdGipcKXnsB +bLCtul/AXv2n7r3hhMoZBTZGznX4yPfzroO2Zf32V5dtfgmB9klsV18MfQxnD3Ee +oAd48LAdgalSp8sG035W84a7e1k6HN7o9BdtEOI9qrDw7UYyFwLCAqrHWyECDyml +WqgQ3/hshSEvt5a26ZW1aOmh6NzjHz/Cogypyivz6tcj2pzMWRwY1AZNEj8S4o5S +p+wwNdUqXRpKeUvew3W1nHgHDH5bzNCsDjXfzebNwIvX4XT2Wubjvvpdk2/XstD+ +XQIDAQAB +-----END PUBLIC KEY----- diff --git a/service/src/test/java/com/lopixlabs/app/fullstackdemo/GenerateToken.java b/service/src/test/java/com/lopixlabs/app/fullstackdemo/GenerateToken.java new file mode 100644 index 00000000..5b6df738 --- /dev/null +++ b/service/src/test/java/com/lopixlabs/app/fullstackdemo/GenerateToken.java @@ -0,0 +1,23 @@ +package com.lopixlabs.app.fullstackdemo; + +import com.lopixlabs.app.fullstackdemo.users.UserRole; +import io.smallrye.jwt.build.Jwt; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import org.eclipse.microprofile.jwt.Claims; + +public class GenerateToken { + /** + * Generate JWT token + */ + public static void main(String[] args) { + String token = + Jwt.issuer("https://localhost.com/issuer") + .upn("jdoe@temp.io") + .groups(new HashSet<>(Arrays.asList(UserRole.ADMIN.name().toLowerCase(Locale.ROOT)))) + .claim(Claims.birthdate.name(), "1973-06-16") + .sign(); + System.out.println(token); + } +} diff --git a/service/src/test/java/com/lopixlabs/app/fullstackdemo/NativeGreetingResourceIT.java b/service/src/test/java/com/lopixlabs/app/fullstackdemo/NativeGreetingResourceIT.java deleted file mode 100644 index 2fcafed2..00000000 --- a/service/src/test/java/com/lopixlabs/app/fullstackdemo/NativeGreetingResourceIT.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.lopixlabs.app.fullstackdemo; - -import io.quarkus.test.junit.NativeImageTest; - -@NativeImageTest -class NativeGreetingResourceIT extends GreetingResourceTest { - - // Execute the same tests but in native mode. -} diff --git a/service/src/test/java/com/lopixlabs/app/fullstackdemo/auth/AuthResourceTest.java b/service/src/test/java/com/lopixlabs/app/fullstackdemo/auth/AuthResourceTest.java new file mode 100644 index 00000000..571f1f28 --- /dev/null +++ b/service/src/test/java/com/lopixlabs/app/fullstackdemo/auth/AuthResourceTest.java @@ -0,0 +1,35 @@ +package com.lopixlabs.app.fullstackdemo.auth; + +import static io.restassured.RestAssured.given; + +import com.lopixlabs.app.fullstackdemo.auth.model.AuthResult; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import java.util.Base64; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; + +@QuarkusTest +class AuthResourceTest { + + @Test + void shouldLogin() { + final AuthResult loginResult = given() + .when() + .body(""" + { + "username": "user", + "password": "password" + } + """) + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .post("/api/auth/signin") + .then() + .statusCode(HttpStatus.SC_OK) + .extract().body().as(AuthResult.class); + + } + +} diff --git a/service/src/test/java/com/lopixlabs/app/fullstackdemo/GreetingResourceTest.java b/service/src/test/java/com/lopixlabs/app/fullstackdemo/hello/GreetingResourceTest.java similarity index 63% rename from service/src/test/java/com/lopixlabs/app/fullstackdemo/GreetingResourceTest.java rename to service/src/test/java/com/lopixlabs/app/fullstackdemo/hello/GreetingResourceTest.java index ddc9c605..39f5161d 100644 --- a/service/src/test/java/com/lopixlabs/app/fullstackdemo/GreetingResourceTest.java +++ b/service/src/test/java/com/lopixlabs/app/fullstackdemo/hello/GreetingResourceTest.java @@ -1,4 +1,4 @@ -package com.lopixlabs.app.fullstackdemo; +package com.lopixlabs.app.fullstackdemo.hello; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; @@ -12,10 +12,10 @@ class GreetingResourceTest { @Test void testHelloEndpoint() { given() - .when().get("/hello") + .when().get("/hello/permit-all") .then() .statusCode(200) - .body("value",is("hello from quarkus!!!")); + .body("value",is("hello + anonymous, isHttps: false, authScheme: null, hasJWT: false")); } } diff --git a/tools/Login.http b/tools/Login.http new file mode 100644 index 00000000..21723114 --- /dev/null +++ b/tools/Login.http @@ -0,0 +1,12 @@ +## LoginResourceApi + +### +# @name apiLoginPost +POST {{host}}/api/login +Content-Type: application/json + +{ + "authentication": "dXNlcjpwYXNz" +} + + diff --git a/tools/api/.openapi-generator/FILES b/tools/api/.openapi-generator/FILES index 587115b8..405f38d0 100644 --- a/tools/api/.openapi-generator/FILES +++ b/tools/api/.openapi-generator/FILES @@ -1,2 +1,3 @@ +Apis/AuthResourceApi.http Apis/GreetingResourceApi.http README.md diff --git a/tools/api/.openapi-generator/openapi.yaml-jetbrains-http-client.sha256 b/tools/api/.openapi-generator/openapi.yaml-jetbrains-http-client.sha256 index 463b9162..23e927b9 100644 --- a/tools/api/.openapi-generator/openapi.yaml-jetbrains-http-client.sha256 +++ b/tools/api/.openapi-generator/openapi.yaml-jetbrains-http-client.sha256 @@ -1 +1 @@ -d624c50784935cfcca1dc30c29b4b047623e40011cd9bf82ec19db74c9422e24 \ No newline at end of file +6d5f8a8d60ea444cdfd2e044ca2a9febf4859bda239197eb81d54290bcfc9a2e \ No newline at end of file diff --git a/tools/api/Apis/GreetingResourceApi.http b/tools/api/Apis/GreetingResourceApi.http index 865f2120..8193b8df 100644 --- a/tools/api/Apis/GreetingResourceApi.http +++ b/tools/api/Apis/GreetingResourceApi.http @@ -1,5 +1,9 @@ ## GreetingResourceApi ### -# @name helloGet -GET http://localhost:8080/hello +# @name helloPermitAllGet +GET http://localhost:8080/hello/permit-all + +### +# @name helloRolesAllowedGet +GET http://localhost:8080/hello/roles-allowed diff --git a/tools/api/README.md b/tools/api/README.md index 21a1396c..bbad8864 100644 --- a/tools/api/README.md +++ b/tools/api/README.md @@ -1,11 +1,11 @@ -# service API - Jetbrains API Client +# Quarkus and svelte demo application - Jetbrains API Client ## OpenAPI File description No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * API basepath : [http://localhost:8080](http://localhost:8080) -* Version : 1.0.0-SNAPSHOT +* Version : 1.0.0 ## Documentation for API Endpoints @@ -13,7 +13,10 @@ All URIs are relative to *http://localhost:8080*, but will link to the `.http` f Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- -*GreetingResourceApi* | [**helloGet**](Apis/GreetingResourceApi.http#helloget) | **GET** /hello | +*AuthResourceApi* | [**apiAuthRefreshGet**](Apis/AuthResourceApi.http#apiauthrefreshget) | **GET** /api/auth/refresh | +*AuthResourceApi* | [**apiAuthSigninPost**](Apis/AuthResourceApi.http#apiauthsigninpost) | **POST** /api/auth/signin | +*GreetingResourceApi* | [**helloPermitAllGet**](Apis/GreetingResourceApi.http#hellopermitallget) | **GET** /hello/permit-all | +*GreetingResourceApi* | [**helloRolesAllowedGet**](Apis/GreetingResourceApi.http#hellorolesallowedget) | **GET** /hello/roles-allowed | diff --git a/tools/http-client.env.json b/tools/http-client.env.json new file mode 100644 index 00000000..cf48461f --- /dev/null +++ b/tools/http-client.env.json @@ -0,0 +1,5 @@ +{ + "dev": { + "host": "http://localhost:8080" + } +} diff --git a/web/.env.development b/web/.env.development index c0930e2d..f7566fdd 100644 --- a/web/.env.development +++ b/web/.env.development @@ -1,2 +1 @@ -VITE_SERVICE_URL=http://localhost:8080 - +VITE_BASE_URL=http://localhost:8080 diff --git a/web/index.html b/web/index.html index 38f38611..fa3531c3 100644 --- a/web/index.html +++ b/web/index.html @@ -1,13 +1,13 @@ - + - - - - Vite App + + + + Full stack demo quarkus + svelte -
- +
+ diff --git a/web/package.json b/web/package.json index 108c5af2..13df8860 100644 --- a/web/package.json +++ b/web/package.json @@ -1,26 +1,37 @@ { - "name": "audio-stream", + "name": "fullstackdemo", "private": true, "version": "0.0.0", + "type": "module", "scripts": { - "start": "vite", - "build": "tsc && vite build", - "preview": "vite preview" - }, - "dependencies": { - "@mantine/core": "^3.6.8", - "@mantine/hooks": "^3.6.8", - "axios": "^0.27.2", - "axios-case-converter": "^0.9.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "dev": "vite --mode development", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json" }, "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.2", + "@tailwindcss/forms": "^0.5.3", + "@tsconfig/svelte": "^3.0.0", + "autoprefixer": "^10.4.7", + "axios": "^1.3.4", + "axios-case-converter": "^1.0.1", + "dayjs": "^1.11.7", + "jwt-decode": "^3.1.2", + "postcss": "^8.4.14", + "postcss-load-config": "^4.0.1", + "radix-icons-svelte": "^1.2.1", + "stwui": "^0.0.36-next", + "svelte": "^3.55.1", + "svelte-check": "^2.10.3", + "svelte-preprocess": "^4.10.7", + "svelte-router-spa": "^6.0.3", + "svelte-use-form": "^2.6.1", + "tailwindcss": "^3.1.5", + "tslib": "^2.5.0", + "typescript": "^4.9.3", + "axios-retry": "^3.4.0", "@types/node": "^18.15.13", - "@types/react": "^18.0.37", - "@types/react-dom": "^18.0.11", - "@vitejs/plugin-react": "^4.0.0", - "typescript": "^4.5.4", "vite": "^4.3.1", "yarn": "^2.4.3" } diff --git a/web/pom.xml b/web/pom.xml index 51634059..a7dcdf29 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -14,8 +14,8 @@ 1.0.0-SNAPSHOT - v17.5.0 - v1.22.17 + v18.14.2 + v1.22.19 diff --git a/web/postcss.config.cjs b/web/postcss.config.cjs new file mode 100644 index 00000000..e48cff58 --- /dev/null +++ b/web/postcss.config.cjs @@ -0,0 +1,13 @@ +const tailwindcss = require("tailwindcss"); +const autoprefixer = require("autoprefixer"); + +const config = { + plugins: [ + //Some plugins, like tailwindcss/nesting, need to run before Tailwind, + tailwindcss(), + //But others, like autoprefixer, need to run after, + autoprefixer, + ], +}; + +module.exports = config; diff --git a/web/src/App.svelte b/web/src/App.svelte new file mode 100644 index 00000000..e905c7ce --- /dev/null +++ b/web/src/App.svelte @@ -0,0 +1,9 @@ + + +
+ +
diff --git a/web/src/App.tsx b/web/src/App.tsx deleted file mode 100644 index fa875f43..00000000 --- a/web/src/App.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import HelloDemo from "./components"; -import {HttpClientProvider} from "./http/HttpClientContext"; -import {MantineProvider} from '@mantine/core' - -function App() { - - const serviceUrl = import.meta.env.VITE_SERVICE_URL as string || ""; - console.log(import.meta.env.MODE); - - return ( - - -

Demo

-
-
-
-
- ); -} - -export default App diff --git a/web/src/AppLayout.svelte b/web/src/AppLayout.svelte new file mode 100644 index 00000000..50cf02ec --- /dev/null +++ b/web/src/AppLayout.svelte @@ -0,0 +1,137 @@ + + + + {#if darkTheme} + + {:else} + + {/if} + Quarkus and Svelte + + +
+ +
+ + + + + + + +
{app.name}
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + v> + + + +
+ + + {#if openMenu} + + + + + + {/if} + diff --git a/web/src/PublicPage.svelte b/web/src/PublicPage.svelte new file mode 100644 index 00000000..21c18f52 --- /dev/null +++ b/web/src/PublicPage.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/web/src/app.postcss b/web/src/app.postcss new file mode 100644 index 00000000..1a7b7cf3 --- /dev/null +++ b/web/src/app.postcss @@ -0,0 +1,4 @@ +/* Write your global styles here, in PostCSS syntax */ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/web/src/app/DashboardPage.svelte b/web/src/app/DashboardPage.svelte new file mode 100644 index 00000000..c04a5b8c --- /dev/null +++ b/web/src/app/DashboardPage.svelte @@ -0,0 +1,26 @@ + +

Admin

+ +User name: {result} +

+ +Refresh diff --git a/web/src/assets/apple-icon.png b/web/src/assets/apple-icon.png new file mode 100644 index 00000000..a20470fa Binary files /dev/null and b/web/src/assets/apple-icon.png differ diff --git a/web/src/assets/favicon.ico b/web/src/assets/favicon.ico new file mode 100644 index 00000000..7482a653 Binary files /dev/null and b/web/src/assets/favicon.ico differ diff --git a/web/src/auth/AuthService.ts b/web/src/auth/AuthService.ts new file mode 100644 index 00000000..6e265fe8 --- /dev/null +++ b/web/src/auth/AuthService.ts @@ -0,0 +1,32 @@ +// store.js +import type {Login} from "../http/generated"; +import {httpClient} from "../http/HttpClient"; +import {navigateTo} from "svelte-router-spa"; +import {dashboardModule} from "../settings"; +import {authData, store} from "./AuthStore"; + +export const authService = { + doLogin: async (username: string, password: string, rememberMe: Boolean) => { + const login: Login = {username, password}; + if (rememberMe) { + store.setLogin(login); + } else { + store.clearLogin(); + } + const response = await httpClient.auth.apiAuthSigninPost(login, + { + headers: { + Authorization: null + } + }); + if (response.status === 200) { + console.log("login ok") + authData.set(response.data); + navigateTo(dashboardModule.href); + } + }, + logout: () => { + store.clearAuthData(); + navigateTo("login"); + } +} diff --git a/web/src/auth/AuthStore.ts b/web/src/auth/AuthStore.ts new file mode 100644 index 00000000..2b8c7a09 --- /dev/null +++ b/web/src/auth/AuthStore.ts @@ -0,0 +1,28 @@ +import type {AuthResult, Login} from "../http/generated"; +import {writable} from "svelte/store"; + +const userKey = 'user'; +const authKey = 'authData'; +const emptyLogin: Login = {username: null, password: null} + +export const store = { + setLogin: (value: Login) => localStorage.setItem(userKey, JSON.stringify(value)), + getLogin: (): Login => JSON.parse(localStorage.getItem(userKey)) || emptyLogin, + clearLogin: () => localStorage.removeItem(userKey), + setAuthData: (value: AuthResult) => localStorage.setItem(authKey, JSON.stringify(value)), + getAuthData: (): AuthResult => JSON.parse(localStorage.getItem(authKey)), + clearAuthData: () => localStorage.removeItem(authKey), +} + +export const login = writable(store.getLogin()) +export const authData = writable(store.getAuthData()) + +const unsubscribe = authData.subscribe(value => { + store.setAuthData(value); +}) + +export function cleanup() { + if (unsubscribe) { + unsubscribe(); + } +} diff --git a/web/src/auth/Login.svelte b/web/src/auth/Login.svelte new file mode 100644 index 00000000..83bcb558 --- /dev/null +++ b/web/src/auth/Login.svelte @@ -0,0 +1,45 @@ + + +
+
+
+
+ login image +
+
+
+ + + + Sign in +
+
+
+
+
diff --git a/web/src/components/HelloDemo.tsx b/web/src/components/HelloDemo.tsx deleted file mode 100644 index 2b4543c5..00000000 --- a/web/src/components/HelloDemo.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import {useEffect, useState} from "react"; -import {Hello} from "http/generated"; -import useHttpClient from "../http/HttpClient"; -import {Blockquote, Loader} from "@mantine/core"; - - -function HelloDemo() { - - const [hello, setHello] = useState(); - const [loading, setLoading] = useState(false); - const httpClient = useHttpClient(); - - useEffect(() => { - const fecth = () => { - setLoading(true); - httpClient.hello.helloGet() - .then((response) => response.data) - .then((json) => setHello(json)) - .finally(() => setLoading(false)); - } - - fecth() - }, []) - - if (loading) { - return - } - - return (<> -
- HelloDemo -
- ); -} - -export default HelloDemo; - diff --git a/web/src/components/PLA.svelte b/web/src/components/PLA.svelte new file mode 100644 index 00000000..e2c775d5 --- /dev/null +++ b/web/src/components/PLA.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/web/src/components/PLButton.svelte b/web/src/components/PLButton.svelte new file mode 100644 index 00000000..4c4d76b6 --- /dev/null +++ b/web/src/components/PLButton.svelte @@ -0,0 +1,3 @@ + diff --git a/web/src/components/PLInput.svelte b/web/src/components/PLInput.svelte new file mode 100644 index 00000000..0a448634 --- /dev/null +++ b/web/src/components/PLInput.svelte @@ -0,0 +1,32 @@ + + +
+ +
diff --git a/web/src/components/PLToggle.svelte b/web/src/components/PLToggle.svelte new file mode 100644 index 00000000..452af02a --- /dev/null +++ b/web/src/components/PLToggle.svelte @@ -0,0 +1,52 @@ + + +
+ +
+ +
+ +
diff --git a/web/src/components/index.ts b/web/src/components/index.ts deleted file mode 100644 index b11ca639..00000000 --- a/web/src/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './HelloDemo'; diff --git a/web/src/favicon.svg b/web/src/favicon.svg deleted file mode 100644 index de4aeddc..00000000 --- a/web/src/favicon.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web/src/http/HttpClient.ts b/web/src/http/HttpClient.ts index 962bb455..cefbfec5 100644 --- a/web/src/http/HttpClient.ts +++ b/web/src/http/HttpClient.ts @@ -1,15 +1,108 @@ +import type {AuthResult} from "./generated"; +import {AuthResourceApiFactory, GreetingResourceApiFactory} from "./generated"; +import applyCaseConverter from "axios-case-converter"; +import axios from "axios"; +import {authData, store} from "../auth/AuthStore"; +import {loginModule} from "../settings"; +import {navigateTo} from "svelte-router-spa"; -import React from 'react'; -import HttpContext from './HttpClientContext'; +let auth: AuthResult = null; +let refresh = false; -const useHttpClient = () => { - const httpClient = React.useContext(HttpContext); +const axiosInstance = applyCaseConverter( + axios.create({ + timeout: 10000, + headers: { + "Content-Type": "application/json" + } + }) +); - if (!httpClient) { - throw new Error('The Http Client was not initialized.'); +function setAuthData(value: AuthResult) { + console.log("token changed"); + auth = value; + axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${auth?.token}`; +} + +authData.subscribe(value => { + setAuthData(value); +}); + +const gotoLogin = () => { + console.log("redirect to login"); + navigateTo(loginModule.href); +} + +axiosInstance.interceptors.response.use(resp => resp, async error => { + if (error.response.status === 401 && !refresh) { + refresh = true; + const response = await refreshToken(); + if (response.status === 200) { + store.setAuthData(response.data); + setAuthData(response.data); + error.config.headers.Authorization = `Bearer ${response.data.token}`; + refresh = false; + return axiosInstance(error.config); + } } + refresh = false; + gotoLogin(); + return +}); + +const refreshToken = async () => { + try { + console.log("refreshToken") + const response = await httpClient.auth.apiAuthRefreshGet( + { + headers: { + Authorization: `Bearer ${auth?.refreshToken}` + } + } + ) + const authResult = response.data; + if (!authResult?.token) { + console.log("refreshToken failed") + store.clearAuthData(); + return response + } + console.log("refreshToken ok") + return response; + } catch (error) { + console.log("refreshToken failed", error) + store.clearAuthData(); + } + } +; - return httpClient; +// axiosInstance.interceptors.request.use(async (config) => { +// if (config.url.endsWith("/auth/signin")) { +// return config; +// } +// let token = auth?.token; +// if (config.url.endsWith("/auth/refresh")) { +// token = auth?.refreshToken; +// } +// if (token) { +// config.headers.Authorization = `Bearer ${token}`; +// } else { +// gotoLogin(); +// } +// return config; +// }, +// function (error) { +// console.log(error); +// return Promise.reject(error); +// } +// ); + +const getFactory = (baseUrl: string) => { + console.log("base url: " + baseUrl); + return { + hello: GreetingResourceApiFactory(undefined, baseUrl, axiosInstance), + auth: AuthResourceApiFactory(undefined, baseUrl, axiosInstance) + } }; -export default useHttpClient; +export const httpClient = getFactory(import.meta.env.VITE_BASE_URL) + diff --git a/web/src/http/HttpClientContext.tsx b/web/src/http/HttpClientContext.tsx deleted file mode 100644 index 6fcb2ab9..00000000 --- a/web/src/http/HttpClientContext.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, {useEffect, useState} from "react"; -import {GreetingResourceApi} from "./generated"; -import {AxiosInstance} from "axios"; -import axiosInstance from './getAxiosInstance'; - -// @ts-ignore -const HttpContext = React.createContext(undefined); - -export default HttpContext; - -const getFactory = (baseUrl: string) => ({ - hello: new GreetingResourceApi(undefined, baseUrl, axiosInstance), -}); - -type HttpClientFactoryContext = ReturnType & { - axiosInstance: AxiosInstance; -}; - -export const HttpClientProvider: React.FC<{ baseUrl: string, children?: React.ReactNode }> = ({ - baseUrl, - children, - }) => { - const [currentBaseUrl, setBaseUrl] = useState(baseUrl); - const [config, setConfig] = useState(() => ({ - ...getFactory(baseUrl), - axiosInstance, - })); - - useEffect(() => { - if (baseUrl === currentBaseUrl) { - return; - } - - setBaseUrl(baseUrl); - setConfig({ - ...getFactory(baseUrl), - axiosInstance, - }); - }, [baseUrl, currentBaseUrl]); - - return {children}; -}; diff --git a/web/src/http/generated/.openapi-generator/openapi.yaml-axios.sha256 b/web/src/http/generated/.openapi-generator/openapi.yaml-axios.sha256 index 463b9162..23e927b9 100644 --- a/web/src/http/generated/.openapi-generator/openapi.yaml-axios.sha256 +++ b/web/src/http/generated/.openapi-generator/openapi.yaml-axios.sha256 @@ -1 +1 @@ -d624c50784935cfcca1dc30c29b4b047623e40011cd9bf82ec19db74c9422e24 \ No newline at end of file +6d5f8a8d60ea444cdfd2e044ca2a9febf4859bda239197eb81d54290bcfc9a2e \ No newline at end of file diff --git a/web/src/http/generated/api.ts b/web/src/http/generated/api.ts index 83cfc1cf..3f1ab503 100644 --- a/web/src/http/generated/api.ts +++ b/web/src/http/generated/api.ts @@ -1,10 +1,10 @@ /* tslint:disable */ /* eslint-disable */ /** - * service API + * Quarkus and svelte demo application * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 1.0.0-SNAPSHOT + * The version of the OpenAPI document: 1.0.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -23,6 +23,25 @@ import type { RequestArgs } from './base'; // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base'; +/** + * + * @export + * @interface AuthResult + */ +export interface AuthResult { + /** + * + * @type {string} + * @memberof AuthResult + */ + 'token'?: string; + /** + * + * @type {string} + * @memberof AuthResult + */ + 'refreshToken'?: string; +} /** * * @export @@ -36,6 +55,182 @@ export interface Hello { */ 'value'?: string; } +/** + * + * @export + * @interface Login + */ +export interface Login { + /** + * + * @type {string} + * @memberof Login + */ + 'username'?: string; + /** + * + * @type {string} + * @memberof Login + */ + 'password'?: string; +} + +/** + * AuthResourceApi - axios parameter creator + * @export + */ +export const AuthResourceApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiAuthRefreshGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/auth/refresh`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {Login} [login] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiAuthSigninPost: async (login?: Login, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/auth/signin`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(login, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * AuthResourceApi - functional programming interface + * @export + */ +export const AuthResourceApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = AuthResourceApiAxiosParamCreator(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiAuthRefreshGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.apiAuthRefreshGet(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {Login} [login] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiAuthSigninPost(login?: Login, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.apiAuthSigninPost(login, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * AuthResourceApi - factory interface + * @export + */ +export const AuthResourceApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = AuthResourceApiFp(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiAuthRefreshGet(options?: any): AxiosPromise { + return localVarFp.apiAuthRefreshGet(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {Login} [login] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiAuthSigninPost(login?: Login, options?: any): AxiosPromise { + return localVarFp.apiAuthSigninPost(login, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * AuthResourceApi - object-oriented interface + * @export + * @class AuthResourceApi + * @extends {BaseAPI} + */ +export class AuthResourceApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AuthResourceApi + */ + public apiAuthRefreshGet(options?: AxiosRequestConfig) { + return AuthResourceApiFp(this.configuration).apiAuthRefreshGet(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {Login} [login] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AuthResourceApi + */ + public apiAuthSigninPost(login?: Login, options?: AxiosRequestConfig) { + return AuthResourceApiFp(this.configuration).apiAuthSigninPost(login, options).then((request) => request(this.axios, this.basePath)); + } +} + /** * GreetingResourceApi - axios parameter creator @@ -48,8 +243,37 @@ export const GreetingResourceApiAxiosParamCreator = function (configuration?: Co * @param {*} [options] Override http request option. * @throws {RequiredError} */ - helloGet: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/hello`; + helloPermitAllGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/hello/permit-all`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + helloRolesAllowedGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/hello/roles-allowed`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -87,8 +311,17 @@ export const GreetingResourceApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async helloGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.helloGet(options); + async helloPermitAllGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.helloPermitAllGet(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async helloRolesAllowedGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.helloRolesAllowedGet(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -106,8 +339,16 @@ export const GreetingResourceApiFactory = function (configuration?: Configuratio * @param {*} [options] Override http request option. * @throws {RequiredError} */ - helloGet(options?: any): AxiosPromise { - return localVarFp.helloGet(options).then((request) => request(axios, basePath)); + helloPermitAllGet(options?: any): AxiosPromise { + return localVarFp.helloPermitAllGet(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + helloRolesAllowedGet(options?: any): AxiosPromise { + return localVarFp.helloRolesAllowedGet(options).then((request) => request(axios, basePath)); }, }; }; @@ -125,8 +366,18 @@ export class GreetingResourceApi extends BaseAPI { * @throws {RequiredError} * @memberof GreetingResourceApi */ - public helloGet(options?: AxiosRequestConfig) { - return GreetingResourceApiFp(this.configuration).helloGet(options).then((request) => request(this.axios, this.basePath)); + public helloPermitAllGet(options?: AxiosRequestConfig) { + return GreetingResourceApiFp(this.configuration).helloPermitAllGet(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof GreetingResourceApi + */ + public helloRolesAllowedGet(options?: AxiosRequestConfig) { + return GreetingResourceApiFp(this.configuration).helloRolesAllowedGet(options).then((request) => request(this.axios, this.basePath)); } } diff --git a/web/src/http/generated/base.ts b/web/src/http/generated/base.ts index 523999f2..45e749c9 100644 --- a/web/src/http/generated/base.ts +++ b/web/src/http/generated/base.ts @@ -1,10 +1,10 @@ /* tslint:disable */ /* eslint-disable */ /** - * service API + * Quarkus and svelte demo application * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 1.0.0-SNAPSHOT + * The version of the OpenAPI document: 1.0.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/http/generated/common.ts b/web/src/http/generated/common.ts index b576a3c3..d1171277 100644 --- a/web/src/http/generated/common.ts +++ b/web/src/http/generated/common.ts @@ -1,10 +1,10 @@ /* tslint:disable */ /* eslint-disable */ /** - * service API + * Quarkus and svelte demo application * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 1.0.0-SNAPSHOT + * The version of the OpenAPI document: 1.0.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/http/generated/configuration.ts b/web/src/http/generated/configuration.ts index abc8b93f..da9100de 100644 --- a/web/src/http/generated/configuration.ts +++ b/web/src/http/generated/configuration.ts @@ -1,10 +1,10 @@ /* tslint:disable */ /* eslint-disable */ /** - * service API + * Quarkus and svelte demo application * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 1.0.0-SNAPSHOT + * The version of the OpenAPI document: 1.0.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/http/generated/index.ts b/web/src/http/generated/index.ts index 2a2ca024..16dfcaf4 100644 --- a/web/src/http/generated/index.ts +++ b/web/src/http/generated/index.ts @@ -1,10 +1,10 @@ /* tslint:disable */ /* eslint-disable */ /** - * service API + * Quarkus and svelte demo application * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 1.0.0-SNAPSHOT + * The version of the OpenAPI document: 1.0.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/http/getAxiosInstance.ts b/web/src/http/getAxiosInstance.ts deleted file mode 100644 index 70fa042c..00000000 --- a/web/src/http/getAxiosInstance.ts +++ /dev/null @@ -1,10 +0,0 @@ -import axios from 'axios'; -import applyCaseConverter from 'axios-case-converter'; - -const axiosInstance = applyCaseConverter( - axios.create({ - timeout: 10000, - }) -); - -export default axiosInstance; diff --git a/web/src/index.css b/web/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/web/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/web/src/lib/Counter.svelte b/web/src/lib/Counter.svelte new file mode 100644 index 00000000..35373844 --- /dev/null +++ b/web/src/lib/Counter.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/web/src/lib/EmptyPage.svelte b/web/src/lib/EmptyPage.svelte new file mode 100644 index 00000000..a854e340 --- /dev/null +++ b/web/src/lib/EmptyPage.svelte @@ -0,0 +1,23 @@ + + +
+

{title}

+
+
+ + + + + + +
+
+ + diff --git a/web/src/lib/Hamburger.svelte b/web/src/lib/Hamburger.svelte new file mode 100644 index 00000000..aeccb316 --- /dev/null +++ b/web/src/lib/Hamburger.svelte @@ -0,0 +1,45 @@ + + + + + diff --git a/web/src/lib/Kbd.svelte b/web/src/lib/Kbd.svelte new file mode 100644 index 00000000..74c4640d --- /dev/null +++ b/web/src/lib/Kbd.svelte @@ -0,0 +1,11 @@ + + +
+ +
diff --git a/web/src/lib/Logo.svelte b/web/src/lib/Logo.svelte new file mode 100644 index 00000000..1af3dc40 --- /dev/null +++ b/web/src/lib/Logo.svelte @@ -0,0 +1,19 @@ + + + + + + + + + + +logo diff --git a/web/src/lib/Navigation.svelte b/web/src/lib/Navigation.svelte new file mode 100644 index 00000000..b74b9bd8 --- /dev/null +++ b/web/src/lib/Navigation.svelte @@ -0,0 +1,82 @@ + + + {#each items as item} + {#if item.children && item.children.length > 0} + {#if item.data} + + + {#each item.children as child} + handleClick(item,child)} + /> + {/each} + + {:else} + + {#each item.children as child} + handleClick(item,child)} + /> + {/each} + + {/if} + {:else if item.data && item.badge} + handleClick(item)} + > + + {item.badge} + + {:else if item.iconData} + handleClick(item)} + > + + + {:else if item.badge} + handleClick(item)} + > + {item.badge} + + {:else} + handleClick(item)} + /> + {/if} + {/each} + + + + diff --git a/web/src/lib/Search.svelte b/web/src/lib/Search.svelte new file mode 100644 index 00000000..5c937915 --- /dev/null +++ b/web/src/lib/Search.svelte @@ -0,0 +1,167 @@ + + + + + + +{#if open} + + + +
+ + + + + {#if value && value.length > 0} + + + + + + {/if} + + + esc + +
+ + {#if fiteredData.length > 0} +
    + {#each fiteredData as item} +
  • handleSelect(item.href)} + on:keypress + > +
    + {#if item.type === 'Component'} + + {:else if item.type === 'Guide'} + + {:else if item.type === 'Action'} + + {:else if item.type === 'Utility'} + + {/if} +
    +
    +

    + {item.name} +

    +

    + {item.type} +

    +
    +
  • + {/each} +
+ {:else} + + + No results found + No results found for this search. Please try again. + + {/if} +
+
+
+{/if} diff --git a/web/src/lib/icons.ts b/web/src/lib/icons.ts new file mode 100644 index 00000000..61dc84d3 --- /dev/null +++ b/web/src/lib/icons.ts @@ -0,0 +1,47 @@ +export const email = +''; +export const phone = +''; +export const lock = +''; +export const brightness_5 = +''; +export const brightness_4 = +''; +export const home = +''; +export const magnify = +''; +export const send = +''; +export const trash = +''; +export const toggle_switch = +''; +export const format_list_numbered = +''; +export const lightning_bolt_circle = +''; +export const function_variant = +''; +export const cog = +''; +export const plus_circle = +''; +export const numeric_1_box = +''; +export const numeric_2_box = +''; +export const account_multiple = +''; +export const folder = +''; +export const file_document = +''; +export const poll = +''; +export const favorite = +''; +export const chart_box = +''; + diff --git a/web/src/lib/interfaces.ts b/web/src/lib/interfaces.ts new file mode 100644 index 00000000..9dfb7733 --- /dev/null +++ b/web/src/lib/interfaces.ts @@ -0,0 +1,41 @@ +import type {SvelteComponent} from "svelte"; + +export interface App { + name: string; + title: string; + description: string; + logo: typeof SvelteComponent +} + +export interface Module { + name: string; + label: string; + href?: string; + iconData?: string; + component: typeof SvelteComponent; +} + +export interface GroupMenuItem { + name: string; + label: string; + href: string; +} + +export interface MenuItem { + name: string; + iconData?: string; + label: string; + badge?: string; + badgeType?: 'info' | 'warn' | 'error' | 'success'; + href: string; + component?: typeof SvelteComponent; + children?: GroupMenuItem[]; +} + +export const toMenu = (module: Module): MenuItem => ({ + name: module.name, + label: module.label, + href: module.href, + iconData: module.iconData, + component: module.component +}); diff --git a/web/src/logo.svg b/web/src/logo.svg deleted file mode 100644 index 6b60c104..00000000 --- a/web/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 00000000..c030412b --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,8 @@ +import "./app.postcss"; +import App from "./App.svelte"; + +const app = new App({ + target: document.getElementById("app"), +}); + +export default app; diff --git a/web/src/main.tsx b/web/src/main.tsx deleted file mode 100644 index fbb54c64..00000000 --- a/web/src/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import './index.css' -import App from './App' - -ReactDOM.render( - - - , - document.getElementById('root') -) diff --git a/web/src/routes.ts b/web/src/routes.ts new file mode 100644 index 00000000..b7660627 --- /dev/null +++ b/web/src/routes.ts @@ -0,0 +1,17 @@ +import AppLayout from "./AppLayout.svelte"; +import PublicPage from "./PublicPage.svelte"; +import {dashboardModule, emptyModule, loginModule} from "./settings"; + + +const userIsAdmin = () => true; + +export const routes = [ + { + name: '/', + component: PublicPage, + layout: AppLayout + }, + {...loginModule}, + {...dashboardModule, layout: AppLayout}, + {...emptyModule, layout: AppLayout}, +] diff --git a/web/src/settings.ts b/web/src/settings.ts new file mode 100644 index 00000000..f75c85d5 --- /dev/null +++ b/web/src/settings.ts @@ -0,0 +1,49 @@ +import type {App, MenuItem, Module} from "./lib/interfaces"; +import {toMenu} from "./lib/interfaces"; +import Logo from "./lib/Logo.svelte"; +import Login from "./auth/Login.svelte"; +import EmptyPage from "./lib/EmptyPage.svelte"; +import DashboardPage from "./app/DashboardPage.svelte"; +import {account_multiple, chart_box, file_document} from './lib/icons'; + + +export let modules = new Map(); + +function add(module: Module) { + modules.set(module.name, module); + return module; +} + +export const dashboardModule = add({name: "dashboard", href: "/dashboard", label: "Dashboard", component: DashboardPage, iconData: chart_box}); +export const loginModule = add({name: "login", href: "/login", label: "Login", component: Login}); +export const emptyModule = add({name: "empty", href: "/empty", label: "Empty Page", component: EmptyPage, iconData:file_document}); + +export const app: App = { + name: 'myApp', + title: 'myApp', + description: 'Quarkus and Svelte fullstack demo', + logo: Logo +} + +export const sideBar: MenuItem[] = [toMenu(dashboardModule), toMenu(emptyModule)]; +export const menuBar: MenuItem[] = [ + { + name: 'team', + iconData: account_multiple, + label: 'Team', + href: '#item2', + children: [ + { + name: 'sub1', + label: 'Sub 1', + href: '#item2-sub1' + }, + { + name: 'sub2', + label: 'Sub 2', + href: '#item2-sub2' + } + ] + } +]; + diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2..00000000 --- a/web/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/web/svelte.config.js b/web/svelte.config.js new file mode 100644 index 00000000..4e7698e2 --- /dev/null +++ b/web/svelte.config.js @@ -0,0 +1,13 @@ +import preprocess from "svelte-preprocess"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: [ + vitePreprocess(), + preprocess({ + postcss: true, + }), + ], +}; diff --git a/web/tailwind.config.cjs b/web/tailwind.config.cjs new file mode 100644 index 00000000..5ff9ceb7 --- /dev/null +++ b/web/tailwind.config.cjs @@ -0,0 +1,11 @@ +module.exports = { + content: [ + './src/**/*.{html,js,svelte,ts}', + './node_modules/stwui/**/*.{svelte,js,ts,html}' + ], + plugins: [ + require('@tailwindcss/forms'), + require('stwui/plugin') + ], + darkMode: 'class', +}; diff --git a/web/tsconfig.json b/web/tsconfig.json index 8cbb0159..c4e1c5fe 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -1,22 +1,20 @@ { + "extends": "@tsconfig/svelte/tsconfig.json", "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, - "skipLibCheck": false, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, "module": "ESNext", - "moduleResolution": "Node", "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "baseUrl": "src" + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "isolatedModules": true }, - "include": ["src"], + "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json index e993792c..65dbdb96 100644 --- a/web/tsconfig.node.json +++ b/web/tsconfig.node.json @@ -1,8 +1,8 @@ { "compilerOptions": { "composite": true, - "module": "esnext", - "moduleResolution": "node" + "module": "ESNext", + "moduleResolution": "Node" }, "include": ["vite.config.ts"] } diff --git a/web/vite.config.ts b/web/vite.config.ts index b1b5f91e..1fd156e5 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,7 +1,10 @@ import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { svelte } from '@sveltejs/vite-plugin-svelte' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()] + plugins: [svelte()], + optimizeDeps: { + exclude: ['radix-icons-svelte'] + } }) diff --git a/web/yarn.lock b/web/yarn.lock index 025ad781..e69de29b 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1,1073 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/code-frame@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" - integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/compat-data@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f" - integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g== - -"@babel/core@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.4.tgz#c6dc73242507b8e2a27fd13a9c1814f9fa34a659" - integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-compilation-targets" "^7.21.4" - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.4" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.4" - "@babel/types" "^7.21.4" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.0" - -"@babel/generator@^7.21.1": - version "7.21.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" - integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== - dependencies: - "@babel/types" "^7.21.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc" - integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA== - dependencies: - "@babel/types" "^7.21.4" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656" - integrity sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg== - dependencies: - "@babel/compat-data" "^7.21.4" - "@babel/helper-validator-option" "^7.21.0" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-function-name@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" - integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== - dependencies: - "@babel/template" "^7.20.7" - "@babel/types" "^7.21.0" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" - integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.2" - "@babel/types" "^7.21.2" - -"@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== - -"@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== - dependencies: - "@babel/types" "^7.20.2" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-option@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" - integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== - -"@babel/helpers@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" - integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== - dependencies: - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.20.7", "@babel/parser@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" - integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== - -"@babel/parser@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" - integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== - -"@babel/plugin-transform-react-jsx-self@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.21.0.tgz#ec98d4a9baafc5a1eb398da4cf94afbb40254a54" - integrity sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-react-jsx-source@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86" - integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/runtime@^7.10.2", "@babel/runtime@^7.13.10": - version "7.17.2" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz" - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/template@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - -"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75" - integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.1" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.2" - "@babel/types" "^7.21.2" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36" - integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.4" - "@babel/types" "^7.21.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.18.6", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.2.tgz#92246f6e00f91755893c2876ad653db70c8310d1" - integrity sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" - integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@emotion/cache@^11.7.1": - version "11.7.1" - resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz" - dependencies: - "@emotion/memoize" "^0.7.4" - "@emotion/sheet" "^1.1.0" - "@emotion/utils" "^1.0.0" - "@emotion/weak-memoize" "^0.2.5" - stylis "4.0.13" - -"@emotion/hash@^0.8.0": - version "0.8.0" - resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" - -"@emotion/memoize@^0.7.4": - version "0.7.5" - resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz" - -"@emotion/react@^11.7.1": - version "11.7.1" - resolved "https://registry.npmjs.org/@emotion/react/-/react-11.7.1.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - "@emotion/cache" "^11.7.1" - "@emotion/serialize" "^1.0.2" - "@emotion/sheet" "^1.1.0" - "@emotion/utils" "^1.0.0" - "@emotion/weak-memoize" "^0.2.5" - hoist-non-react-statics "^3.3.1" - -"@emotion/serialize@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz" - dependencies: - "@emotion/hash" "^0.8.0" - "@emotion/memoize" "^0.7.4" - "@emotion/unitless" "^0.7.5" - "@emotion/utils" "^1.0.0" - csstype "^3.0.2" - -"@emotion/sheet@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz" - -"@emotion/unitless@^0.7.5": - version "0.7.5" - resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" - -"@emotion/utils@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz" - -"@emotion/weak-memoize@^0.2.5": - version "0.2.5" - resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz" - -"@esbuild/android-arm64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz#52c3e6cabc19c5e4c1c0c01cb58f0442338e1c14" - integrity sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg== - -"@esbuild/android-arm@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.11.tgz#f3fc768235aecbeb840d0049fdf13cd28592105f" - integrity sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw== - -"@esbuild/android-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.11.tgz#443ed47771a7e917e4282469ba350d117473550c" - integrity sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ== - -"@esbuild/darwin-arm64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz#0e8c78d94d5759a48521dbfd83189d2ed3499a16" - integrity sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw== - -"@esbuild/darwin-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz#2405cfdf70eb961c7cf973463ca7263dc2004c88" - integrity sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw== - -"@esbuild/freebsd-arm64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz#d5138e873e15f87bd4564c024dfa00ef37e623fd" - integrity sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q== - -"@esbuild/freebsd-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz#e850b58b8fabf8e9ef0e125af3c25229ad2d6c38" - integrity sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g== - -"@esbuild/linux-arm64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz#2bfb93d0809ec2357c12ebb27736b750c9ae0aa5" - integrity sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg== - -"@esbuild/linux-arm@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz#e56fb3b76828317a704f4a167c5bd790fe5314e7" - integrity sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg== - -"@esbuild/linux-ia32@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz#59fa1c49b271793d14eb5effc757e8c0d0cb2cab" - integrity sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA== - -"@esbuild/linux-loong64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz#89575bc189099c03a36daa54f3f481780c7fd502" - integrity sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g== - -"@esbuild/linux-mips64el@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz#0e18ca039dc7e4645efd8edc1b10952933eb6b1b" - integrity sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw== - -"@esbuild/linux-ppc64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz#2d152cb3a253afb8c100a165ad132dc96f36cb11" - integrity sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA== - -"@esbuild/linux-riscv64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz#c6ac494a81221d53d65b33e665c7df1747952d3c" - integrity sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA== - -"@esbuild/linux-s390x@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz#4bad33894bc7415cea4be8fa90fe456226a424ad" - integrity sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ== - -"@esbuild/linux-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz#903fda743459f530a16a6c6ee8d2c0f6c1a12fc7" - integrity sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw== - -"@esbuild/netbsd-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz#b589239fe7d9b16ee03c5e191f3f5b640f1518a1" - integrity sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag== - -"@esbuild/openbsd-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz#b355019754116bef39ec688f8fd2fe6471b9779b" - integrity sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w== - -"@esbuild/sunos-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz#2ea47fb592e68406e5025a7696dc714fc6a115dc" - integrity sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg== - -"@esbuild/win32-arm64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz#47e6fdab17c4c52e6e0d606dd9cb843b29826325" - integrity sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ== - -"@esbuild/win32-ia32@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz#a97273aa3164c8d8f501899f55cc75a4a79599a3" - integrity sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw== - -"@esbuild/win32-x64@0.17.11": - version "0.17.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz#9be796d93ae27b636da32d960899a4912bca27a1" - integrity sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ== - -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz" - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@mantine/core@^3.6.8": - version "3.6.9" - resolved "https://registry.npmjs.org/@mantine/core/-/core-3.6.9.tgz" - dependencies: - "@mantine/styles" "3.6.9" - "@popperjs/core" "^2.9.3" - "@radix-ui/react-scroll-area" "^0.1.1" - clsx "^1.1.1" - react-popper "^2.2.5" - react-textarea-autosize "^8.3.2" - -"@mantine/hooks@^3.6.8": - version "3.6.9" - resolved "https://registry.npmjs.org/@mantine/hooks/-/hooks-3.6.9.tgz" - -"@mantine/styles@3.6.9": - version "3.6.9" - resolved "https://registry.npmjs.org/@mantine/styles/-/styles-3.6.9.tgz" - dependencies: - "@emotion/cache" "^11.7.1" - "@emotion/react" "^11.7.1" - "@emotion/serialize" "^1.0.2" - "@emotion/utils" "^1.0.0" - clsx "^1.1.1" - csstype "^3.0.9" - -"@popperjs/core@^2.9.3": - version "2.11.2" - resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz" - -"@radix-ui/number@0.1.0": - version "0.1.0" - resolved "https://registry.npmjs.org/@radix-ui/number/-/number-0.1.0.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/primitive@0.1.0": - version "0.1.0" - resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-0.1.0.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-compose-refs@0.1.0": - version "0.1.0" - resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-context@0.1.1": - version "0.1.1" - resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-0.1.1.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-presence@0.1.1": - version "0.1.1" - resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-0.1.1.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "0.1.0" - "@radix-ui/react-use-layout-effect" "0.1.0" - -"@radix-ui/react-primitive@0.1.3": - version "0.1.3" - resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-slot" "0.1.2" - -"@radix-ui/react-scroll-area@^0.1.1": - version "0.1.3" - resolved "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-0.1.3.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/number" "0.1.0" - "@radix-ui/primitive" "0.1.0" - "@radix-ui/react-compose-refs" "0.1.0" - "@radix-ui/react-context" "0.1.1" - "@radix-ui/react-presence" "0.1.1" - "@radix-ui/react-primitive" "0.1.3" - "@radix-ui/react-use-callback-ref" "0.1.0" - "@radix-ui/react-use-direction" "0.1.0" - "@radix-ui/react-use-layout-effect" "0.1.0" - -"@radix-ui/react-slot@0.1.2": - version "0.1.2" - resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-0.1.2.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "0.1.0" - -"@radix-ui/react-use-callback-ref@0.1.0": - version "0.1.0" - resolved "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.1.0.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-use-direction@0.1.0": - version "0.1.0" - resolved "https://registry.npmjs.org/@radix-ui/react-use-direction/-/react-use-direction-0.1.0.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-use-layout-effect@0.1.0": - version "0.1.0" - resolved "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz" - dependencies: - "@babel/runtime" "^7.13.10" - -"@types/node@^18.15.13": - version "18.15.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" - integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== - -"@types/prop-types@*": - version "15.7.4" - resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz" - -"@types/react-dom@^18.0.11": - version "18.0.11" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33" - integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^18.0.37": - version "18.0.37" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.37.tgz#7a784e2a8b8f83abb04dc6b9ed9c9b4c0aee9be7" - integrity sha512-4yaZZtkRN3ZIQD3KSEwkfcik8s0SWV+82dlJot1AbGYHCzJkWP3ENBY6wYeDRmKZ6HkrgoGAmR2HqdwYGp6OEw== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" - -"@vitejs/plugin-react@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz#46d1c37c507447d10467be1c111595174555ef28" - integrity sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ== - dependencies: - "@babel/core" "^7.21.4" - "@babel/plugin-transform-react-jsx-self" "^7.21.0" - "@babel/plugin-transform-react-jsx-source" "^7.19.6" - react-refresh "^0.14.0" - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - dependencies: - color-convert "^1.9.0" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios-case-converter@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/axios-case-converter/-/axios-case-converter-0.9.0.tgz#9f47f641ae3f8c04a5395370601342e4f8c33c77" - integrity sha512-G6tOanR1cdIrBtRyiPvR+xXyWCCZacSw8LtT8bmImvpye+XiyoOlLluz0N61IfetznNQ9BeMi5yA0rIyYDr8mQ== - dependencies: - camel-case "^4.1.1" - header-case "^2.0.3" - snake-case "^3.0.3" - tslib "^2.3.0" - -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - -browserslist@^4.21.3: - version "4.21.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== - dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" - -camel-case@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -caniuse-lite@^1.0.30001449: - version "1.0.30001458" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz#871e35866b4654a7d25eccca86864f411825540c" - integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w== - -capital-case@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669" - integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - upper-case-first "^2.0.2" - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -clsx@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" - dependencies: - safe-buffer "~5.1.1" - -csstype@^3.0.2, csstype@^3.0.9: - version "3.0.10" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz" - -debug@^4.1.0: - version "4.3.3" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz" - dependencies: - ms "2.1.2" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -electron-to-chromium@^1.4.284: - version "1.4.315" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.315.tgz#b60a6676b3a1db332cfc8919118344aa06b9ac99" - integrity sha512-ndBQYz3Eyy3rASjjQ9poMJGoAlsZ/aZnq6GBsGL4w/4sWIAwiUHVSsMuADbxa8WJw7pZ0oxLpGbtoDt4vRTdCg== - -esbuild@^0.17.5: - version "0.17.11" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.11.tgz#9f3122643b21d7e7731e42f18576c10bfa28152b" - integrity sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg== - optionalDependencies: - "@esbuild/android-arm" "0.17.11" - "@esbuild/android-arm64" "0.17.11" - "@esbuild/android-x64" "0.17.11" - "@esbuild/darwin-arm64" "0.17.11" - "@esbuild/darwin-x64" "0.17.11" - "@esbuild/freebsd-arm64" "0.17.11" - "@esbuild/freebsd-x64" "0.17.11" - "@esbuild/linux-arm" "0.17.11" - "@esbuild/linux-arm64" "0.17.11" - "@esbuild/linux-ia32" "0.17.11" - "@esbuild/linux-loong64" "0.17.11" - "@esbuild/linux-mips64el" "0.17.11" - "@esbuild/linux-ppc64" "0.17.11" - "@esbuild/linux-riscv64" "0.17.11" - "@esbuild/linux-s390x" "0.17.11" - "@esbuild/linux-x64" "0.17.11" - "@esbuild/netbsd-x64" "0.17.11" - "@esbuild/openbsd-x64" "0.17.11" - "@esbuild/sunos-x64" "0.17.11" - "@esbuild/win32-arm64" "0.17.11" - "@esbuild/win32-ia32" "0.17.11" - "@esbuild/win32-x64" "0.17.11" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - -follow-redirects@^1.14.9: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - -header-case@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063" - integrity sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q== - dependencies: - capital-case "^1.0.4" - tslib "^2.0.3" - -hoist-non-react-statics@^3.3.1: - version "3.3.2" - resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" - dependencies: - react-is "^16.7.0" - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" - -json5@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -loose-envify@^1.0.0, loose-envify@^1.1.0: - version "1.4.0" - resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== - -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - -postcss@^8.4.21: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== - dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -react-dom@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" - integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.0" - -react-fast-compare@^3.0.1: - version "3.2.0" - resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz" - -react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" - -react-popper@^2.2.5: - version "2.2.5" - resolved "https://registry.npmjs.org/react-popper/-/react-popper-2.2.5.tgz" - dependencies: - react-fast-compare "^3.0.1" - warning "^4.0.2" - -react-refresh@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" - integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== - -react-textarea-autosize@^8.3.2: - version "8.3.3" - resolved "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz" - dependencies: - "@babel/runtime" "^7.10.2" - use-composed-ref "^1.0.0" - use-latest "^1.0.0" - -react@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" - integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== - dependencies: - loose-envify "^1.1.0" - -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" - -rollup@^3.20.2: - version "3.20.6" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.6.tgz#53c0fd73e397269d2ce5f0ec12851457dd53cacd" - integrity sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw== - optionalDependencies: - fsevents "~2.3.2" - -safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - -scheduler@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" - integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== - dependencies: - loose-envify "^1.1.0" - -semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - -snake-case@^3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" - integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" - -stylis@4.0.13: - version "4.0.13" - resolved "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - dependencies: - has-flag "^3.0.0" - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" - -tslib@^2.0.3, tslib@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - -typescript@^4.5.4: - version "4.5.5" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz" - -update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -upper-case-first@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324" - integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg== - dependencies: - tslib "^2.0.3" - -use-composed-ref@^1.0.0: - version "1.2.1" - resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.2.1.tgz" - -use-isomorphic-layout-effect@^1.0.0: - version "1.1.1" - resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz" - -use-latest@^1.0.0: - version "1.2.0" - resolved "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz" - dependencies: - use-isomorphic-layout-effect "^1.0.0" - -vite@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.1.tgz#9badb1377f995632cdcf05f32103414db6fbb95a" - integrity sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg== - dependencies: - esbuild "^0.17.5" - postcss "^8.4.21" - rollup "^3.20.2" - optionalDependencies: - fsevents "~2.3.2" - -warning@^4.0.2: - version "4.0.3" - resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz" - dependencies: - loose-envify "^1.0.0" - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yarn@^2.4.3: - version "2.4.3" - resolved "https://registry.npmjs.org/yarn/-/yarn-2.4.3.tgz"