diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..4293868b --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '27 0 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'java', 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Java JDK + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 24 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.gitignore b/.gitignore index 013ce37f..de09ea1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target/ !.mvn/wrapper/maven-wrapper.jar +/src/main/resources/static ### STS ### #.apt_generated @@ -21,4 +22,5 @@ build/ nbbuild/ dist/ nbdist/ -.nb-gradle/ \ No newline at end of file +.nb-gradle/ +/bin/ diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..e76d1f32 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index 9cc84ea9..2cc7d4a5 100644 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index c3150437..bdca1ae0 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,2 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar \ No newline at end of file diff --git a/.project b/.project index cf1bd39d..24ca68a3 100644 --- a/.project +++ b/.project @@ -1,15 +1,12 @@ - AngularAndSpring + angularandspring + angularandspring-backend + angularandspring-frontend - - org.eclipse.jdt.core.javabuilder - - - org.eclipse.m2e.core.maven2Builder @@ -17,7 +14,6 @@ - org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 839d647e..99f26c02 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,5 +1,2 @@ eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding//src/test/java=UTF-8 encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 714351ae..00000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.jboss.ide.eclipse.as.core.prefs b/.settings/org.jboss.ide.eclipse.as.core.prefs deleted file mode 100644 index cf3aa3a9..00000000 --- a/.settings/org.jboss.ide.eclipse.as.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -org.jboss.ide.eclipse.as.core.singledeployable.deployableList= diff --git a/.settings/ts.eclipse.ide.core.prefs b/.settings/ts.eclipse.ide.core.prefs deleted file mode 100644 index 370e6988..00000000 --- a/.settings/ts.eclipse.ide.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -typeScriptBuildPath={} diff --git a/.travis.yml b/.travis.yml index eb2bb3b9..0f48ad4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,10 @@ language: java jdk: - - oraclejdk8 + - oraclejdk11 + +addons: + chrome: beta services: - docker @@ -13,9 +16,9 @@ notifications: on_failure: always before_install: - - nvm install 6.9 - - nvm use 6.9 + - nvm install 14.15 + - nvm use 14.15 script: - mvn clean install + mvn clean install -Ddocker=true diff --git a/Dockerfile b/Dockerfile index 9f4e2e20..00f56c09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM openjdk:8-jdk-alpine +FROM eclipse-temurin:24-jdk VOLUME /tmp -RUN sh -c 'touch /app.jar' -ADD target/trader-0.0.1-SNAPSHOT.jar app.jar -ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication" +ARG JAR_FILE +ADD backend/target/${JAR_FILE} /app.jar +ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:+UseStringDeduplication -XX:MaxDirectMemorySize=64m" ENTRYPOINT exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar diff --git a/README.md b/README.md index c8113a0a..f71f8837 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,74 @@ -# This is an example application to show howto use Spring Boot, Angular and Mongodb with the Webflux features of Spring. +# This is an example application to show howto use Spring Boot, Angular and Mongodb with the reactive Webflux features of Spring. -![Build Status](https://travis-ci.org/Angular2Guy/AngularAndSpring.svg?branch=master) +[![CodeQL](https://github.com/Angular2Guy/AngularAndSpring/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/Angular2Guy/AngularAndSpring/actions/workflows/codeql-analysis.yml) Author: Sven Loesekann -Technologies: Angular, Angular-Cli, Angular-Material, Typescript, Spring Boot, Spring Webflux, MongoDB, Maven, Docker +Technologies: Angular, Angular-Cli, Angular-Material, Typescript, Spring Boot, Spring Webflux, Spring Security, MongoDB, Maven, Docker, ArchUnit, Kafka, Kafka-Streams, Spring Actuator with Prometheus interface + +## Articles +* [Using KRaft Kafka for development and Kubernetes deployment](https://angular2guy.wordpress.com/2024/08/17/using-kraft-kafka-for-development-and-kubernetes-deployment/) +* [Errorhandling with Spring Webclient and Reactor](https://angular2guy.wordpress.com/2022/10/05/errorhandling-with-spring-webclient-and-reactor/) +* [Spring Boot 3 update experience](https://angular2guy.wordpress.com/2022/11/15/spring-boot-3-update-experience/) +* [Reactive Kafka with Streaming in Spring Boot Part 1](https://angular2guy.wordpress.com/2022/05/23/reactive-kafka-with-streaming-in-spring-boot-part-1/) +* [Reactive Kafka with Streaming in Spring Boot Part 2](https://angular2guy.wordpress.com/2022/06/09/reactive-kafka-with-streaming-in-spring-boot-part-2/) +* [Reactive Kafka with Streaming in Spring Boot Part 3](https://angular2guy.wordpress.com/2022/06/10/reactive-kafka-with-streaming-in-spring-boot-part-3/) +* [Performance improvement for Getter/Setter access with LambdaMetafactory](https://angular2guy.wordpress.com/2022/05/12/angularandspring-uses-lambdametafactory-for-getter-setter-access/) +* [Spring Boot/MongoDb Performance Analysis and Improvements](https://angular2guy.wordpress.com/2022/02/15/spring-boot-mongodb-performance-analysis-and-improvements/) +* [Ngx-Simple-Charts multiline and legend support howto](https://angular2guy.wordpress.com/2021/10/02/ngx-simple-charts-multiline-and-legend-support-howto/) +* [Deep Links With Angular Routing and i18n in Prod Mode](https://angular2guy.wordpress.com/2021/07/31/deep-links-with-angular-routing-and-i18n-in-prod-mode/) +* [Developing and Using Angular Libraries](https://angular2guy.wordpress.com/2021/07/31/developing-and-using-angular-libraries/) +* [How to Modularize an Angular Application](https://angular2guy.wordpress.com/2022/04/16/how-to-modularize-an-angular-application/) +* [Deployment Setup for Spring Boot Apps With MongoDB and Kubernetes](https://dzone.com/articles/a-developmentdeployment-setup-for-an-angular-sprin) +* [Using Angular and Reactive Spring With JWT Tokens](https://dzone.com/articles/angular-and-reactive-spring-with-jwt-tokens) +* [Angular and Spring Webflux](https://dzone.com/articles/angular-and-spring-webflux) ## What is the goal? -The goal is to be reactive from top to bottom. To do that the project uses Angular in the frontend and Spring Boot with Reactive Web as server. Mongodb is the database connected with the reactive MongoDB driver. That enables a reactive chain from the browser to the DB. +The goal is to be reactive from top to bottom. To do that the project uses Angular in the frontend and Spring Boot with Reactive Web as server. Mongodb is the database connected with the reactive MongoDB driver. That enables a reactive chain from the browser to the DB. The security is done with Jwt Tokens and the logged out tokens are invalidated. The project uses an in memory MongoDB to be just cloned build and ready to run. It serves as an example for clean architecture. The architecture is checked with ArchUnit in a test. The health and performance of the application can be monitored with Spring Actuator with Prometheus interface. With the 'kafka' and 'prod' profiles the Kafka support can be used for Jwt token revokation(Minikube setups(development/system) available). ## What is it? -The application runs a scheduled task reads the exchange rates of cryptocurrencies and stores them in the Mongodb. The UI uses the rest service to read the rates and displays them on a table. The table updates itself regularly. A detail page shows the data of the currency and a chart of the rates of the current day, 7 days, 30 days, 90 days. -If the user logs in the user can see the relevant part of the orderbooks for an order. +The application runs a scheduled task reads the exchange rates of cryptocurrencies and stores them in the Mongodb. The UI uses the rest service to read the rates and displays them on a table. The table updates itself regularly and shows out of date data in blue. A detail page shows the data of the currency and a chart of the rates of the current day, 7 days, 30 days, 90 days. +If the user logs in the user can see the relevant part of the orderbooks for an order. The orderbooks route is implemented as a lazy loading feature module. The route guard checks for the Jwt token and the logout invalidates the Jwt token. + +## C4 Architecture Diagrams +The project has a [System Context Diagram](structurizr/diagrams/structurizr-1-SystemContext.svg), a [Container Diagram](structurizr/diagrams/structurizr-1-Containers.svg) and a [Component Diagram](structurizr/diagrams/structurizr-1-Components.svg). The Diagrams have been created with Structurizr. The file runStructurizr.sh contains the commands to use Structurizr and the directory structurizr contains the dsl file. ## Data Import and Preparation -The application has two scheduled jobs. The first is the ScheduledTask class. It reads the rates of the crypto currencies once a minute with different initial delays. That job provides one mongodb collection per exchange. The collections can have different documents with currency pairs like Usd to BitCoin or Eur to Ether or one document with all currency pairs, depends on what the exchanges provide. These collections provide the data for the current day chart and the current quote. To display the 7 day, 30 day, 90 day charts, hourly or daily quotes are required. Once a day the PrepareData class runs jobs to calculate the hourly and daily quotes. The jobs run between 0 and 2 o’clock. If no values are available the for the timeframe(hour, day) a value of zero is shown. For the 7 day chart the hourly data is used and for the 30 and 90 day charts the daily data is used. The SchedulingConfig class provides a config that provides the scheduler with 5 threads to enable the running of ScheduledTask class for the imports and the PrepareData class for aggregation concurrently. +The application has two scheduled jobs. The first is the ScheduledTask class. It reads the rates of the crypto currencies once a minute with different initial delays. That job provides one mongodb collection per exchange. The collections can have different documents with currency pairs like Usd to BitCoin or Eur to Ether or one document with all currency pairs, depends on what the exchanges provide. These collections provide the data for the current day chart and the current quote. To display the 7 day, 30 day, 90 day charts, hourly or daily quotes are required. Once a day the PrepareData class runs jobs to calculate the hourly and daily quotes. The jobs run between 0 and 4 o’clock. If no values are available the for the timeframe(hour, day) a value of zero is shown. For the 7 day chart the hourly data is used and for the 30 and 90 day charts the daily data is used. The Schedulers class provides a an elastic bounded scheduler with enough threads for each client(connection issues) of the ScheduledTask class for the quote imports. The aggregation jobs are run asynchronous(as @Async method) on application startup(@EventListener(ApplicationReadyEvent.class)) and the scheduled runs (@Scheduled(cron=...)) to do the calculation outside of the reactor event loop. The aggregation jobs are started only once(@SchedulerLock) in intervals with @Scheduled to separate them and to reduce the database load. + +## Minikube setup + +The application can now be run in a Minikube cluster with a Helm chart. The setup has a persistent volume to store the files of mongodb. A setup of mongodb with the volume and a setup for the application. It can be found in the minikube directory as a Helm chart. It uses the resource limit support of Jdk 16+ to limit memory. Kubernetes limits the cpu use and uses the startupprobes and livenessprobes that Spring Actuator provides. A Helm chart for the Kafka development setup in Minikube can be found in the directory 'minikube/kafka'. A Helm chart for the deployment of Kafka/Zookeeper/AngularAndSpring/MongoDb system setup can be found in the directory 'minikube/angularandspringwithkafka'. Further documentation can be found in the [Blog](https://angular2guy.wordpress.com) articles. + +## Monitoring +The Spring Actuator interface with Prometheus interface can be used as it is described in this article: + +[Monitoring Spring Boot with Prometheus and Grafana](https://ordina-jworks.github.io/monitoring/2020/11/16/monitoring-spring-prometheus-grafana.html) + +To test the setup the application has to be started and the Docker Images for Prometheus and Grafana have to be started and configured. The scripts 'runGraphana.sh' and 'runPrometheus.sh' can be used as a starting point. +The Spring Actuator configuration shows primarily the http performance and the Gc pauses. More metrics can be enabled in the application.properties file. + +## Jvm Memory management + +The memory state and other values of the Jvm can be watched with the jstat tool that is included in the jdk. To watch the memory of a running Jvm this command can be used: + +jstat -gcutil -h 10 insert_process_id 1000 ## Setup -MongoDB 3.4.x or newer. +MongoDB 4.4.x or newer. -Eclipse Oxygen JEE or newer. +Eclipse IDE for Enterprise Java and Web Developers newest version. -Plugin Typescript.Java 1.4.0 or newer. +Java 21 or newer -Maven 3.3.3 or newer. +Maven 3.9.5 or newer -Nodejs 6.9.x or newer +Nodejs 18.19.x or newer -Npm 3.10.x or newer +Npm 10.2.x or newer -Angular Cli 1.4.0 or newer. +Angular Cli 18 or newer. diff --git a/.classpath b/backend/.classpath similarity index 73% rename from .classpath rename to backend/.classpath index 6d7587a8..0ec47b64 100644 --- a/.classpath +++ b/backend/.classpath @@ -9,16 +9,26 @@ + + - + + + + + + + + + diff --git a/backend/.project b/backend/.project new file mode 100644 index 00000000..014704a5 --- /dev/null +++ b/backend/.project @@ -0,0 +1,23 @@ + + + angularandspring-backend + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/backend/.settings/org.eclipse.core.resources.prefs b/backend/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..29abf999 --- /dev/null +++ b/backend/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/backend/.settings/org.eclipse.jdt.core.prefs b/backend/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..36705116 --- /dev/null +++ b/backend/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,16 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=24 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=21 diff --git a/backend/.settings/org.eclipse.m2e.core.prefs b/backend/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/backend/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 00000000..85241211 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,190 @@ + + + 4.0.0 + angularandspring-backend + jar + angularandspring-backend + Demo project for Spring Boot + + ch.xxx + angularandspring + 0.0.1-SNAPSHOT + + + + Apache License, Version 2.0 + repo + http://www.apache.org/licenses/LICENSE-2.0.html + + + + + org.springframework.boot + spring-boot-starter-data-mongodb-reactive + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-validation + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + io.micrometer + micrometer-registry-prometheus + runtime + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.kafka + spring-kafka + + + org.apache.kafka + kafka-streams + + + io.projectreactor.kafka + reactor-kafka + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + net.javacrumbs.shedlock + shedlock-spring + 6.0.1 + + + net.javacrumbs.shedlock + shedlock-provider-mongo-reactivestreams + 6.0.1 + + + net.sf.jasperreports + jasperreports + 6.17.0 + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + com.tngtech.archunit + archunit-junit5 + 1.4.0 + test + + + + + + org.springdoc + springdoc-openapi-maven-plugin + 1.3 + + + integration-test + + generate + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + + lifecycle-mapping + org.eclipse.m2e + 1.0.0 + + + + + + exec-maven-plugin + org.codehaus.mojo + [3.1.0,) + + exec + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/META-INF/MANIFEST.MF b/backend/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000..5e949512 --- /dev/null +++ b/backend/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/src/main/java/ch/xxx/trader/TraderApplication.java b/backend/src/main/java/ch/xxx/trader/TraderApplication.java similarity index 80% rename from src/main/java/ch/xxx/trader/TraderApplication.java rename to backend/src/main/java/ch/xxx/trader/TraderApplication.java index c56bfee8..17420307 100644 --- a/src/main/java/ch/xxx/trader/TraderApplication.java +++ b/backend/src/main/java/ch/xxx/trader/TraderApplication.java @@ -17,11 +17,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.scheduling.annotation.EnableScheduling; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; @SpringBootApplication -@ComponentScan +@OpenAPIDefinition(info = @Info(title = "Trader API", version = "1.0", description = "Crypto Currency Information")) public class TraderApplication { public static void main(String[] args) { diff --git a/src/main/java/ch/xxx/trader/clients/MongoDbClient.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/MongoDbClient.java similarity index 83% rename from src/main/java/ch/xxx/trader/clients/MongoDbClient.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/MongoDbClient.java index 47353a14..58dd5437 100644 --- a/src/main/java/ch/xxx/trader/clients/MongoDbClient.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/MongoDbClient.java @@ -13,17 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.scheduling.annotation.EnableScheduling; -import ch.xxx.trader.TraderApplication; - @SpringBootApplication @EnableScheduling @ComponentScan diff --git a/src/main/java/ch/xxx/trader/clients/MongoDbConfiguration.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/MongoDbConfiguration.java similarity index 95% rename from src/main/java/ch/xxx/trader/clients/MongoDbConfiguration.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/MongoDbConfiguration.java index 25363fea..4d01c368 100644 --- a/src/main/java/ch/xxx/trader/clients/MongoDbConfiguration.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/MongoDbConfiguration.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients; import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration; import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories; @@ -27,7 +27,7 @@ public class MongoDbConfiguration extends AbstractReactiveMongoConfiguration { @Override protected String getDatabaseName() { - return "trader-test"; + return "traderdb"; } @Override diff --git a/backend/src/main/java/ch/xxx/trader/adapter/clients/RestOrderBookClient.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/RestOrderBookClient.java new file mode 100644 index 00000000..5a27be28 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/RestOrderBookClient.java @@ -0,0 +1,55 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.clients; + +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +import ch.xxx.trader.domain.common.WebUtils; +import ch.xxx.trader.domain.services.MyOrderBookClient; +import reactor.core.publisher.Mono; + +@Service +public class RestOrderBookClient implements MyOrderBookClient { + private static final String URLBF = "https://api.bitfinex.com"; + private static final String URLBS = "https://www.bitstamp.net/api"; + private static final String URLIB = "https://api.itbit.com"; + + public Mono getOrderbookBitfinex(String currpair) { + WebClient wc = this.buildWebClient(URLBF); + return wc.get().uri("/v1/book/" + currpair + "/").accept(MediaType.APPLICATION_JSON) + .exchangeToMono(res -> res.bodyToMono(String.class)); + } + + public Mono getOrderbookBitstamp(String currpair) { + WebClient wc = this.buildWebClient(URLBS); + return wc.get().uri("/v2/order_book/" + currpair + "/").accept(MediaType.APPLICATION_JSON) + .exchangeToMono(res -> res.bodyToMono(String.class)); + } + + public Mono getOrderbookItbit(String currpair) { + WebClient wc = WebUtils.buildWebClient(URLIB); + return wc.get().uri("/v1/markets/" + currpair + "/order_book").accept(MediaType.APPLICATION_JSON) + .exchangeToMono(res -> res.bodyToMono(String.class)); + } + + private WebClient buildWebClient(String url) { + ReactorClientHttpConnector connector = new ReactorClientHttpConnector(); + return WebClient.builder().clientConnector(connector).baseUrl(url).build(); + } +} diff --git a/src/main/java/ch/xxx/trader/clients/RestClientBitfinex.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientBitfinex.java similarity index 83% rename from src/main/java/ch/xxx/trader/clients/RestClientBitfinex.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientBitfinex.java index 4e4b4743..2f8ad821 100644 --- a/src/main/java/ch/xxx/trader/clients/RestClientBitfinex.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientBitfinex.java @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients.test; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; -import ch.xxx.trader.dtos.QuoteBf; +import ch.xxx.trader.domain.model.entity.QuoteBf; public class RestClientBitfinex { private static final String URL = "https://api.bitfinex.com"; @@ -26,7 +26,7 @@ public class RestClientBitfinex { public static void main(String[] args) { WebClient wc = WebClient.create(URL); QuoteBf quote = wc.get().uri("/v1/pubticker/xrpusd") - .accept(MediaType.APPLICATION_JSON).exchange().flatMap(response -> response.bodyToMono(QuoteBf.class)) + .accept(MediaType.APPLICATION_JSON).exchangeToMono(response -> response.bodyToMono(QuoteBf.class)) .map(res -> {res.setPair("xprusd");return res;}).block(); System.out.println(quote.toString()); } diff --git a/src/main/java/ch/xxx/trader/clients/RestClientBitstamp.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientBitstamp.java similarity index 83% rename from src/main/java/ch/xxx/trader/clients/RestClientBitstamp.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientBitstamp.java index 0829cd2c..5c0c5e16 100644 --- a/src/main/java/ch/xxx/trader/clients/RestClientBitstamp.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientBitstamp.java @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients.test; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; -import ch.xxx.trader.dtos.QuoteBs; +import ch.xxx.trader.domain.model.entity.QuoteBs; public class RestClientBitstamp { private static final String URL = "https://www.bitstamp.net/api"; @@ -26,7 +26,7 @@ public class RestClientBitstamp { public static void main(String[] args) { WebClient wc = WebClient.create(URL); QuoteBs quote = wc.get().uri("/v2/ticker/xrpeur/") - .accept(MediaType.APPLICATION_JSON).exchange().flatMap(response -> response.bodyToMono(QuoteBs.class)) + .accept(MediaType.APPLICATION_JSON).exchangeToMono(response -> response.bodyToMono(QuoteBs.class)) .map(res -> {res.setPair("xrpeur");return res;}).block(); System.out.println(quote.toString()); } diff --git a/src/main/java/ch/xxx/trader/clients/RestClientCoinbase.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientCoinbase.java similarity index 91% rename from src/main/java/ch/xxx/trader/clients/RestClientCoinbase.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientCoinbase.java index 7f9527e9..5bcf92b9 100644 --- a/src/main/java/ch/xxx/trader/clients/RestClientCoinbase.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientCoinbase.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients.test; import java.io.BufferedReader; import java.io.IOException; @@ -28,8 +28,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import ch.xxx.trader.dtos.QuoteCb; -import ch.xxx.trader.dtos.WrapperCb; +import ch.xxx.trader.domain.model.dto.WrapperCb; +import ch.xxx.trader.domain.model.entity.QuoteCb; import reactor.core.publisher.Mono; public class RestClientCoinbase { @@ -40,8 +40,7 @@ public static void main(String[] args) { // client.testIt(); WebClient wc = WebClient.create(URL); QuoteCb quoteCb = wc.get().uri("/exchange-rates?currency=BTC") - .accept(MediaType.APPLICATION_JSON_UTF8).exchange() - .flatMap(response -> response.bodyToMono(WrapperCb.class)) + .accept(MediaType.APPLICATION_JSON).exchangeToMono(response -> response.bodyToMono(WrapperCb.class)) .flatMap(resp -> Mono.just(resp.getData())) .flatMap(resp2 -> Mono.just(resp2.getRates())) .block(); diff --git a/src/main/java/ch/xxx/trader/clients/RestClientItbit.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientItbit.java similarity index 82% rename from src/main/java/ch/xxx/trader/clients/RestClientItbit.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientItbit.java index a4749e0b..4619cf98 100644 --- a/src/main/java/ch/xxx/trader/clients/RestClientItbit.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/RestClientItbit.java @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients.test; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; -import ch.xxx.trader.dtos.QuoteIb; +import ch.xxx.trader.domain.model.entity.QuoteIb; public class RestClientItbit { @@ -27,7 +27,7 @@ public class RestClientItbit { public static void main(String[] args) { WebClient wc = WebClient.create(URL); QuoteIb quote = wc.get().uri("/v1/markets/XBTUSD/ticker") - .accept(MediaType.APPLICATION_JSON).exchange().flatMap(response -> response.bodyToMono(QuoteIb.class)) + .accept(MediaType.APPLICATION_JSON).exchangeToMono(response -> response.bodyToMono(QuoteIb.class)) .block(); System.out.println(quote.toString()); } diff --git a/src/main/java/ch/xxx/trader/clients/WebsocketClient.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/WebsocketClient.java similarity index 97% rename from src/main/java/ch/xxx/trader/clients/WebsocketClient.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/test/WebsocketClient.java index 946c05dd..bfe8dde6 100644 --- a/src/main/java/ch/xxx/trader/clients/WebsocketClient.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/WebsocketClient.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients.test; import java.math.BigDecimal; import java.net.URI; @@ -25,7 +25,7 @@ import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; import org.springframework.web.reactive.socket.client.WebSocketClient; -import ch.xxx.trader.dtos.QuoteBf; +import ch.xxx.trader.domain.model.entity.QuoteBf; import reactor.core.publisher.Mono; public class WebsocketClient { diff --git a/src/main/java/ch/xxx/trader/clients/WsClient.java b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/WsClient.java similarity index 97% rename from src/main/java/ch/xxx/trader/clients/WsClient.java rename to backend/src/main/java/ch/xxx/trader/adapter/clients/test/WsClient.java index ce6f64c3..cd59cc9f 100644 --- a/src/main/java/ch/xxx/trader/clients/WsClient.java +++ b/backend/src/main/java/ch/xxx/trader/adapter/clients/test/WsClient.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.clients; +package ch.xxx.trader.adapter.clients.test; import java.net.URI; import java.time.Duration; diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/ApplicationConfig.java b/backend/src/main/java/ch/xxx/trader/adapter/config/ApplicationConfig.java new file mode 100644 index 00000000..240bda9d --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/ApplicationConfig.java @@ -0,0 +1,75 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import java.time.Duration; + +import javax.net.ssl.SSLException; + +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import ch.xxx.trader.usecase.common.DtoUtils; +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; +import reactor.netty.tcp.SslProvider.SslContextSpec; + +@Configuration +public class ApplicationConfig { + + @Bean + public ObjectMapper createObjectMapper() { + return DtoUtils.produceObjectMapper(); + } + + @Bean + public WebProperties.Resources resources() { + return new WebProperties.Resources(); + } + + @Bean + public WebClient.Builder createWebClient() { + ConnectionProvider provider = ConnectionProvider.builder("Client").maxConnections(20) + .maxIdleTime(Duration.ofSeconds(6)).maxLifeTime(Duration.ofSeconds(7)) + .pendingAcquireTimeout(Duration.ofSeconds(9L)).evictInBackground(Duration.ofSeconds(10)).build(); + + var webClientBuilder = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient + .create(provider).secure(spec -> sslTimeouts(spec)).option(ChannelOption.SO_KEEPALIVE, false) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000) + .doOnConnected( + c -> c.addHandlerLast(new ReadTimeoutHandler(6)).addHandlerLast(new WriteTimeoutHandler(7))) + .responseTimeout(Duration.ofSeconds(7L)))); + return webClientBuilder; + } + + private void sslTimeouts(SslContextSpec spec) { + try { + spec.sslContext(SslContextBuilder.forClient().build()).handshakeTimeout(Duration.ofSeconds(8)) + .closeNotifyFlushTimeout(Duration.ofSeconds(6)).closeNotifyReadTimeout(Duration.ofSeconds(6)); + } catch (SSLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/ExceptionLoggingFilter.java b/backend/src/main/java/ch/xxx/trader/adapter/config/ExceptionLoggingFilter.java new file mode 100644 index 00000000..bd157abf --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/ExceptionLoggingFilter.java @@ -0,0 +1,85 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.security.web.firewall.RequestRejectedException; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.GenericFilterBean; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class ExceptionLoggingFilter extends GenericFilterBean { + private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionLoggingFilter.class); + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + try { + chain.doFilter(req, res); + } catch (RequestRejectedException exception) { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + record MyEntry(String key, Object value) { + } + LOGGER.info(String.format("Exception: %s", exception.getMessage())); + LOGGER.info("Remote Ip: {}", request.getRemoteAddr()); + LOGGER.info("Request URL: {}", request.getRequestURL()); + Map attributeMap = Collections.list(request.getAttributeNames()).stream() + .flatMap(attName -> Stream.of(new MyEntry(attName, request.getAttribute(attName)))) + .collect(Collectors.toMap(myEntry -> myEntry.key, myEntry -> myEntry.value)); + LOGGER.debug("Request Attributes: {}", this.createStringFromMap(attributeMap)); + Map headerMap = Collections.list(request.getHeaderNames()).stream() + .flatMap(headerName -> Stream.of(new MyEntry(headerName, request.getHeader(headerName)))) + .collect(Collectors.toMap(myEntry -> myEntry.key, myEntry -> myEntry.value)); + LOGGER.info("Request Headers: {}", this.createStringFromMap(headerMap)); + LOGGER.info("Request Body length: {}", request.getContentLength()); + try { + LOGGER.debug("Request Body content: {}", new String(request.getInputStream().readAllBytes())); + } catch (IOException e) { + LOGGER.warn("Failed to display body.", e); + } + + LOGGER.warn("request_rejected: remote={}, user_agent={}, request_url={}", request.getRemoteHost(), + request.getHeader(HttpHeaders.USER_AGENT), request.getRequestURL(), exception); + + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } + } + + private String createStringFromMap(Map myMap) { + return myMap.entrySet().stream() + .map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue() == null ? "" : entry.getValue())) + .collect(Collectors.joining(" | ")); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/FlapDoodleConfig.java b/backend/src/main/java/ch/xxx/trader/adapter/config/FlapDoodleConfig.java new file mode 100644 index 00000000..ed741f90 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/FlapDoodleConfig.java @@ -0,0 +1,62 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +//@Configuration +public class FlapDoodleConfig { + /* + private static final Logger LOGGER = LoggerFactory.getLogger(FlapDoodleConfig.class); + private static final int MONGO_DB_PORT = 27017; + private MongodExecutable mongodExecutable = null; + private MongodProcess mongod = null; + @Value("${server.port:}") + private String serverPort; + @Value("${spring.profiles.active:}") + private String activeProfiles; + + @PostConstruct + public void initMongoDb() { + if (this.serverPort.isBlank() || this.serverPort.contains("8080") || this.serverPort.matches("\\d") + && (this.activeProfiles.isBlank() || !this.activeProfiles.toLowerCase().contains("prod"))) { + try { + MongodStarter starter = MongodStarter.getDefaultInstance(); + MongodConfig mongodConfig = MongodConfig.builder().version(Version.Main.V4_4) + .net(new Net(MONGO_DB_PORT, Network.localhostIsIPv6())).build(); + this.mongodExecutable = starter.prepare(mongodConfig); + this.mongod = this.mongodExecutable.start(); + LOGGER.info("MongoDb process: {}, state: {}", this.mongod.getProcessId(), + this.mongod.isProcessRunning()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @PreDestroy + public void stopMongoDb() { + if (this.serverPort.isBlank() || this.serverPort.contains("8080") || this.serverPort.matches("\\d") + && (this.activeProfiles.isBlank() || !this.activeProfiles.toLowerCase().contains("prod"))) { + try { + this.mongodExecutable.stop(); + LOGGER.info("MongoDb proces: {}, state: {}", this.mongod.getProcessId(), + this.mongod.isProcessRunning()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + */ +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/ForwardServletFilter.java b/backend/src/main/java/ch/xxx/trader/adapter/config/ForwardServletFilter.java new file mode 100644 index 00000000..1586d6e7 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/ForwardServletFilter.java @@ -0,0 +1,73 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@WebFilter +@Component +public class ForwardServletFilter implements Filter { + private static final Logger LOG = LoggerFactory.getLogger(ForwardServletFilter.class); + public static final List SUPPORTED_LOCALES = List.of(Locale.ENGLISH, Locale.GERMAN); + public static final List REST_PATHS = List.of("/bitfinex", "/bitstamp", "/coinbase", "/itbit", "/myuser", + "/statistics", "/actuator", "/swagger-ui.html", "/swagger-ui", "/v3"); + public static final List LANGUAGE_PATHS = SUPPORTED_LOCALES.stream() + .map(myLocale -> String.format("/%s/", myLocale.getLanguage())).collect(Collectors.toList()); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest myRequest = (HttpServletRequest) request; +// LOG.info(String.format("ServletPath: %s", myRequest.getServletPath())); + if (REST_PATHS.stream() +// .peek(restEndPoint -> LOG.info(restEndPoint + " " + myRequest.getServletPath() + " " +// + myRequest.getServletPath().indexOf(restEndPoint))) + .anyMatch(restEndPoint -> 0 == myRequest.getServletPath().indexOf(restEndPoint)) + || (LANGUAGE_PATHS.stream() +// .peek(langPath -> LOG.info(langPath + " " + myRequest.getServletPath() + " " + myRequest.getServletPath().indexOf(langPath))) + .anyMatch(langPath -> 0 == myRequest.getServletPath().indexOf(langPath)) + && (myRequest.getServletPath().contains(".") && !myRequest.getServletPath().contains("?")))) { + chain.doFilter(myRequest, response); + } else { + Iterable iterable = () -> myRequest.getLocales().asIterator(); + Locale userLocale = StreamSupport.stream(iterable.spliterator(), false) + .filter(SUPPORTED_LOCALES::contains).findFirst().orElse(Locale.ENGLISH); + String forwardPath = String.format("/%s/index.html", userLocale.getLanguage()); +// LOG.info(String.format("Forward to: %s", forwardPath)); + RequestDispatcher dispatcher = myRequest.getServletContext().getRequestDispatcher(forwardPath); + dispatcher.forward(myRequest, response); + return; + } + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/GlobalExceptionHandler.java b/backend/src/main/java/ch/xxx/trader/adapter/config/GlobalExceptionHandler.java new file mode 100644 index 00000000..ac208a4f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/GlobalExceptionHandler.java @@ -0,0 +1,74 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import jakarta.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import com.mongodb.MongoTimeoutException; + +import ch.xxx.trader.domain.exceptions.AuthenticationException; +import io.netty.handler.timeout.TimeoutException; + +@ControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler({ MongoTimeoutException.class, TimeoutException.class }) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + public Object handleException(final Exception exception, final HttpServletRequest request) { +// record MyEntry(String key, Object value) { +// } + LOGGER.info(String.format("Execption: %s", exception.getMessage()), exception); + LOGGER.info("Remote Ip: {}", request.getRemoteAddr()); + LOGGER.info("Request URL: {}", request.getRequestURL()); +// Map attributeMap = Collections.list(request.getAttributeNames()).stream() +// .flatMap(attName -> Stream.of(new MyEntry(attName, request.getAttribute(attName)))) +// .collect(Collectors.toMap(myEntry -> myEntry.key, myEntry -> myEntry.value)); +// LOGGER.debug("Request Attributes: {}", this.createStringFromMap(attributeMap)); +// Map headerMap = Collections.list(request.getHeaderNames()).stream() +// .flatMap(headerName -> Stream.of(new MyEntry(headerName, request.getHeader(headerName)))) +// .collect(Collectors.toMap(myEntry -> myEntry.key, myEntry -> myEntry.value)); +// LOGGER.info("Request Headers: {}", this.createStringFromMap(headerMap)); +// LOGGER.info("Request Body length: {}", request.getContentLength()); +// try { +// LOGGER.debug("Request Body content: {}", new String(request.getInputStream().readAllBytes())); +// } catch (IOException e) { +// LOGGER.warn("Failed to display body.", e); +// } + return new Object(); + } + + @ExceptionHandler({ AuthenticationException.class }) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + public Object handleAuthenticationException(final Exception exception, final HttpServletRequest request) { + LOGGER.trace("AuthenticationException", exception); + return new Object(); + } + +// private String createStringFromMap(Map myMap) { +// return myMap.entrySet().stream() +// .map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue() == null ? "" : entry.getValue())) +// .collect(Collectors.joining(" | ")); +// } +} \ No newline at end of file diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/JwtTokenFilter.java b/backend/src/main/java/ch/xxx/trader/adapter/config/JwtTokenFilter.java new file mode 100644 index 00000000..0250e72f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/JwtTokenFilter.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + +import ch.xxx.trader.usecase.services.JwtTokenService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; + +public class JwtTokenFilter extends GenericFilterBean { + private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenFilter.class); + + private JwtTokenService jwtTokenProvider; + + public JwtTokenFilter(JwtTokenService jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) + throws IOException, ServletException { + + String token = jwtTokenProvider.resolveToken((HttpServletRequest) req); + if (token != null && jwtTokenProvider.validateToken(token)) { + Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null; + SecurityContextHolder.getContext().setAuthentication(auth); + } else { + LOGGER.debug("Token rejected: {}", token); + } + + filterChain.doFilter(req, res); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/KafkaConfig.java b/backend/src/main/java/ch/xxx/trader/adapter/config/KafkaConfig.java new file mode 100644 index 00000000..5c8d61c5 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/KafkaConfig.java @@ -0,0 +1,139 @@ +package ch.xxx.trader.adapter.config; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +import jakarta.annotation.PostConstruct; + +import org.apache.kafka.clients.DefaultHostResolver; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.config.TopicConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.annotation.EnableKafkaStreams; +import org.springframework.kafka.config.TopicBuilder; +import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer; + +import reactor.kafka.receiver.ReceiverOptions; +import reactor.kafka.sender.KafkaSender; +import reactor.kafka.sender.SenderOptions; + +@Configuration +@Profile("kafka | prod") +@EnableKafka +@EnableKafkaStreams +public class KafkaConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConfig.class); + private static final String GZIP = "gzip"; + private static final String ZSTD = "zstd"; + private static final String SPRING_DESERIALIZER = "spring.deserializer.value.delegate.class"; + public static final String NEW_USER_TOPIC = "new-user-topic"; + public static final String NEW_USER_DLT_TOPIC = "new-user-topic-retry"; + public static final String USER_LOGOUT_SOURCE_TOPIC = "user-logout-source-topic"; + public static final String USER_LOGOUT_SINK_TOPIC = "user-logout-sink-topic"; + public static final String USER_LOGOUT_SINK_DLT_TOPIC = "user-logout-sink-topic-retry"; + + private SenderOptions senderOptions; + private ReceiverOptions receiverOptions; + @Value("${spring.kafka.bootstrap-servers}") + private String bootstrapServers; + @Value("${spring.kafka.producer.compression-type}") + private String compressionType; + @Value("${spring.kafka.producer.key-serializer}") + private String producerKeySerializer; + @Value("${spring.kafka.producer.value-serializer}") + private String producerValueSerializer; + @Value("${spring.kafka.producer.enable.idempotence}") + private boolean enableIdempotence; + @Value("${spring.kafka.consumer.group-id}") + private String consumerGroupId; + @Value("${spring.kafka.consumer.auto-offset-reset}") + private String consumerAutoOffsetReset; + @Value("${spring.kafka.consumer.key-deserializer}") + private String consumerKeySerializer; + @Value("${spring.kafka.consumer.value-deserializer}") + private String consumerValueSerializer; + + @PostConstruct + public void init() throws ClassNotFoundException { + String bootstrap = this.bootstrapServers.split(":")[0].trim(); + if (bootstrap.matches("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$")) { + DefaultHostResolver.IP_ADDRESS = bootstrap; + } else if (!bootstrap.isEmpty()) { + DefaultHostResolver.KAFKA_SERVICE_NAME = bootstrap; + } + LOGGER.info("Kafka Servername: {} Kafka Servicename: {} Ip Address: {}", DefaultHostResolver.KAFKA_SERVER_NAME, + DefaultHostResolver.KAFKA_SERVICE_NAME, DefaultHostResolver.IP_ADDRESS); + Map props = new HashMap<>(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + //props.put(ProducerConfig.CLIENT_ID_CONFIG, "sample-producer"); + props.put(ProducerConfig.ACKS_CONFIG, "all"); + props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); + props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, this.compressionType); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, Class.forName(this.producerKeySerializer)); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, Class.forName(this.producerValueSerializer)); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, Class.forName(this.producerKeySerializer)); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, Class.forName(this.producerKeySerializer)); + this.senderOptions = SenderOptions.create(props); +// this.senderOptions.maxInFlight(10); + props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + //props.put(ProducerConfig.CLIENT_ID_CONFIG, "sample-producer"); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "all"); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, this.consumerAutoOffsetReset); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, Class.forName(this.consumerKeySerializer)); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, Class.forName(this.consumerValueSerializer)); + props.put(SPRING_DESERIALIZER, this.consumerKeySerializer); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class); + this.receiverOptions = ReceiverOptions.create(props); + this.receiverOptions.pollTimeout(Duration.ofSeconds(1L)); + } + + @Bean + public ReceiverOptions kafkaReceiverOptions() { + return this.receiverOptions; + } + + @Bean + public KafkaSender kafkaSender() { + return KafkaSender.create(this.senderOptions); + } + + @Bean + public NewTopic newUserTopic() { + return TopicBuilder.name(KafkaConfig.NEW_USER_TOPIC) + .config(TopicConfig.COMPRESSION_TYPE_CONFIG, this.compressionType).compact().build(); + } + + @Bean + public NewTopic newUserDltTopic() { + return TopicBuilder.name(KafkaConfig.NEW_USER_TOPIC) + .config(TopicConfig.COMPRESSION_TYPE_CONFIG, this.compressionType).compact().build(); + } + + @Bean + public NewTopic userLogoutSourceTopic() { + return TopicBuilder.name(KafkaConfig.USER_LOGOUT_SOURCE_TOPIC) + .config(TopicConfig.COMPRESSION_TYPE_CONFIG, this.compressionType).compact().build(); + } + + @Bean + public NewTopic userLogoutSinkTopic() { + return TopicBuilder.name(KafkaConfig.USER_LOGOUT_SINK_TOPIC) + .config(TopicConfig.COMPRESSION_TYPE_CONFIG, this.compressionType).compact().build(); + } + + @Bean + public NewTopic userLogoutSinkDltTopic() { + return TopicBuilder.name(KafkaConfig.USER_LOGOUT_SINK_DLT_TOPIC) + .config(TopicConfig.COMPRESSION_TYPE_CONFIG, this.compressionType).compact().build(); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/SchedulingConfig.java b/backend/src/main/java/ch/xxx/trader/adapter/config/SchedulingConfig.java new file mode 100644 index 00000000..ae6b1a17 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/SchedulingConfig.java @@ -0,0 +1,59 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import java.util.concurrent.Executor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import io.micrometer.core.aop.TimedAspect; +import io.micrometer.core.instrument.MeterRegistry; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; + +@Configuration +@EnableAspectJAutoProxy +@EnableScheduling +@EnableAsync +@EnableSchedulerLock(defaultLockAtMostFor = "10m") +public class SchedulingConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingConfig.class); + + @Bean + TimedAspect timedAspect(MeterRegistry registry) { + return new TimedAspect(registry); + } + + @Bean(name = "clientTaskExecutor") + public Executor threadPoolTaskExecutor() { + return this.createThreadPoolTaskExecutor(20); + } + + private Executor createThreadPoolTaskExecutor(int maxPoolSize) { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(1); + executor.setKeepAliveSeconds(1); + executor.setAllowCoreThreadTimeOut(true); + return executor; + } +} \ No newline at end of file diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/SpringMongoConfig.java b/backend/src/main/java/ch/xxx/trader/adapter/config/SpringMongoConfig.java new file mode 100644 index 00000000..2ecfbcd9 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/SpringMongoConfig.java @@ -0,0 +1,83 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.mongo.MongoProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.http.codec.support.DefaultServerCodecConfigurer; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import com.mongodb.reactivestreams.client.MongoClient; +import com.mongodb.reactivestreams.client.MongoClients; + +import jakarta.annotation.PostConstruct; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider; + +@Configuration +public class SpringMongoConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(SpringMongoConfig.class); + private static final String SCHED_LOCK_DB = "schedLock"; + @Value("${spring.data.mongodb.uri:}") + private String mongoDbUri; + @Value("${MONGODB_HOST:}") + private String mongoDbHost; + private final MongoProperties mongoProperties; + + public SpringMongoConfig(MongoProperties mongoProperties) { + this.mongoProperties = mongoProperties; + } + + @PostConstruct + public void init() { + LOGGER.info("MongoDbUri: {}", this.mongoDbUri); + LOGGER.info("MongoDbHost: {}", this.mongoDbHost); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public MongoClient mongoClient() { +// LOGGER.info("MongoPort: {}", this.mongoProperties.getPort()); + LOGGER.info("MongoUri: {}", this.mongoDbUri.replace("27017", + this.mongoProperties.getPort() == null ? "27017" : this.mongoProperties.getPort().toString())); + MongoClient mongoClient = MongoClients.create(this.mongoDbUri.replace("27017", + this.mongoProperties.getPort() == null || this.mongoProperties.getPort() < 1 ? "27017" + : this.mongoProperties.getPort().toString())); + mongoClient.getClusterDescription().getShortDescription(); + LOGGER.info("MongoClusterShortDescription: {}", mongoClient.getClusterDescription().getShortDescription()); + return mongoClient; + } + + @Bean + public ServerCodecConfigurer serverCodecConfigurer() { + return new DefaultServerCodecConfigurer(); + } + + @Bean + public LockProvider lockProvider(MongoClient mongo) { + return new ReactiveStreamsMongoLockProvider(mongo.getDatabase(SCHED_LOCK_DB)); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/config/WebSecurityConfig.java b/backend/src/main/java/ch/xxx/trader/adapter/config/WebSecurityConfig.java new file mode 100644 index 00000000..70157f3b --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/config/WebSecurityConfig.java @@ -0,0 +1,62 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.config; + +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import ch.xxx.trader.domain.common.Role; +import ch.xxx.trader.usecase.services.JwtTokenService; + +@EnableWebSecurity +@Configuration +@Order(SecurityProperties.DEFAULT_FILTER_ORDER) +public class WebSecurityConfig { + + private final JwtTokenService jwtTokenProvider; + + public WebSecurityConfig(JwtTokenService jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Bean + public SecurityFilterChain configure(HttpSecurity http) throws Exception { + JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider); + HttpSecurity httpSecurity = http + .authorizeHttpRequests(authorize -> authorize + .requestMatchers(AntPathRequestMatcher.antMatcher("/*/*/orderbook"), + AntPathRequestMatcher.antMatcher("/*/*/*/orderbook")) + .hasAuthority(Role.USERS.toString()).requestMatchers(AntPathRequestMatcher.antMatcher("/**")) + .permitAll()) + .csrf(myCsrf -> myCsrf.disable()) + .sessionManagement(mySm -> mySm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .headers(myHeaders -> myHeaders.contentSecurityPolicy(myCsp -> myCsp.policyDirectives( + "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"))) + .headers(myHeaders -> myHeaders.xssProtection(myXss -> myXss.headerValue(HeaderValue.ENABLED))) + .headers(myHeaders -> myHeaders.frameOptions(myFo -> myFo.sameOrigin())) + .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + return httpSecurity.build(); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/controller/BitfinexController.java b/backend/src/main/java/ch/xxx/trader/adapter/controller/BitfinexController.java new file mode 100644 index 00000000..71180ace --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/controller/BitfinexController.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.controller; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ch.xxx.trader.domain.model.entity.QuoteBf; +import ch.xxx.trader.usecase.services.BitfinexService; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/bitfinex") +public class BitfinexController { + private final BitfinexService bitfinexService; + + public BitfinexController(BitfinexService bitfinexService) { + this.bitfinexService = bitfinexService; + } + + @GetMapping("/{currpair}/orderbook") + public Mono getOrderbook(@PathVariable String currpair) { + return this.bitfinexService.getOrderbook(currpair); + } + + @GetMapping("/{pair}/current") + public Mono currentQuote(@PathVariable String pair) { + return this.bitfinexService.currentQuote(pair); + } + + @GetMapping("/{pair}/{timeFrame}") + public Flux tfQuotes(@PathVariable String timeFrame, @PathVariable String pair) { + return this.bitfinexService.tfQuotes(timeFrame, pair); + } + + @GetMapping(path="/{pair}/{timeFrame}/pdf", produces=MediaType.APPLICATION_PDF_VALUE) + public Mono pdfReport(@PathVariable String timeFrame, @PathVariable String pair) { + return this.bitfinexService.pdfReport(timeFrame, pair); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/controller/BitstampController.java b/backend/src/main/java/ch/xxx/trader/adapter/controller/BitstampController.java new file mode 100644 index 00000000..6a5d7b71 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/controller/BitstampController.java @@ -0,0 +1,57 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.controller; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ch.xxx.trader.domain.model.entity.QuoteBs; +import ch.xxx.trader.usecase.services.BitstampService; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/bitstamp") +public class BitstampController { + private final BitstampService bitstampService; + + public BitstampController(BitstampService bitstampService) { + this.bitstampService = bitstampService; + } + + @GetMapping("/{currpair}/orderbook") + public Mono getOrderbook(@PathVariable String currpair) { + return this.bitstampService.getOrderbook(currpair); + } + + @GetMapping("/{pair}/current") + public Mono currentQuoteBtc(@PathVariable String pair) { + return this.bitstampService.currentQuoteBtc(pair); + } + + @GetMapping("/{pair}/{timeFrame}") + public Flux tfQuotesBtc(@PathVariable String timeFrame, @PathVariable String pair) { + return this.bitstampService.tfQuotesBtc(timeFrame, pair); + } + + @GetMapping(path="/{pair}/{timeFrame}/pdf", produces=MediaType.APPLICATION_PDF_VALUE) + public Mono pdfReport(@PathVariable String timeFrame, @PathVariable String pair) { + return this.bitstampService.pdfReport(timeFrame, pair); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/controller/CoinbaseController.java b/backend/src/main/java/ch/xxx/trader/adapter/controller/CoinbaseController.java new file mode 100644 index 00000000..315ae0c3 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/controller/CoinbaseController.java @@ -0,0 +1,71 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ch.xxx.trader.domain.model.entity.QuoteCb; +import ch.xxx.trader.domain.model.entity.QuoteCbSmall; +import ch.xxx.trader.usecase.services.CoinbaseService; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/coinbase") +public class CoinbaseController { + private final CoinbaseService coinbaseService; + + public CoinbaseController(CoinbaseService coinbaseService) { + this.coinbaseService = coinbaseService; + } + + @GetMapping("/today") + public Flux todayQuotesBc() { + return this.coinbaseService.todayQuotesBc(); + } + + @GetMapping("/7days") + public Flux sevenDaysQuotesBc() { + return this.coinbaseService.sevenDaysQuotesBc(); + } + + @GetMapping("/30days") + public Flux thirtyDaysQuotesBc() { + return this.coinbaseService.thirtyDaysQuotesBc(); + } + + @GetMapping("/90days") + public Flux nintyDaysQuotesBc() { + return this.coinbaseService.nintyDaysQuotesBc(); + } + + @GetMapping("/6month") + public Flux sixMonthsQuotesBc() { + return this.coinbaseService.sixMonthsQuotesBc(); + } + + @GetMapping("/1year") + public Flux oneYearQuotesBc() { + return this.coinbaseService.oneYearQuotesBc(); + } + + @GetMapping("/current") + public Mono currentQuoteBc() { + return this.coinbaseService.currentQuoteBc(); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/controller/ItbitController.java b/backend/src/main/java/ch/xxx/trader/adapter/controller/ItbitController.java new file mode 100644 index 00000000..c75a596c --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/controller/ItbitController.java @@ -0,0 +1,59 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.controller; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ch.xxx.trader.domain.model.entity.QuoteIb; +import ch.xxx.trader.usecase.services.ItbitService; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/itbit") +public class ItbitController { + private final ItbitService itbitService; + + public ItbitController(ItbitService itbitService) { + this.itbitService = itbitService; + } + + @GetMapping("/{currpair}/orderbook") + public Mono getOrderbook(@PathVariable String currpair, HttpServletRequest request) { + return this.itbitService.getOrderbook(currpair); + } + + @GetMapping("/{pair}/current") + public Mono currentQuote(@PathVariable String pair) { + return this.itbitService.currentQuote(pair); + } + + @GetMapping("/{pair}/{timeFrame}") + public Flux tfQuotes(@PathVariable String timeFrame, @PathVariable String pair) { + return this.itbitService.tfQuotes(timeFrame, pair); + } + + @GetMapping(path="/{pair}/{timeFrame}/pdf", produces=MediaType.APPLICATION_PDF_VALUE) + public Mono pdfReport(@PathVariable String timeFrame, @PathVariable String pair) { + return this.itbitService.pdfReport(timeFrame, pair); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/controller/MyUserController.java b/backend/src/main/java/ch/xxx/trader/adapter/controller/MyUserController.java new file mode 100644 index 00000000..48462bb2 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/controller/MyUserController.java @@ -0,0 +1,74 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.controller; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ch.xxx.trader.domain.model.dto.AuthCheck; +import ch.xxx.trader.domain.model.dto.RefreshTokenDto; +import ch.xxx.trader.domain.model.entity.MyUser; +import ch.xxx.trader.domain.services.MyUserService; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/myuser") +public class MyUserController { + private final MyUserService myUserService; + + public MyUserController(MyUserService myUserService) { + this.myUserService = myUserService; + } + + @PostMapping("/authorize") + public Mono postAuthorize(@RequestBody AuthCheck authcheck, @RequestHeader Map header) { + return this.myUserService.postAuthorize(authcheck, header); + } + + @PostMapping("/signin") + public Mono postUserSignin(@RequestBody MyUser myUser) + throws NoSuchAlgorithmException, InvalidKeySpecException { + return this.myUserService.postUserSignin(myUser); + } + + @PutMapping("/logout") + public Mono postLogout(@RequestHeader(value = HttpHeaders.AUTHORIZATION) String bearerStr) { + return this.myUserService.postLogout(bearerStr); + } + + @PostMapping("/login") + public Mono postUserLogin(@RequestBody MyUser myUser,HttpServletRequest request) + throws NoSuchAlgorithmException, InvalidKeySpecException { + return this.myUserService.postUserLogin(myUser); + } + + @GetMapping("/refreshToken") + public Mono getRefreshToken(@RequestHeader(value = HttpHeaders.AUTHORIZATION) String bearerStr) { + return this.myUserService.refreshToken(bearerStr); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/controller/StatisticsController.java b/backend/src/main/java/ch/xxx/trader/adapter/controller/StatisticsController.java new file mode 100644 index 00000000..6bb1f84f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/controller/StatisticsController.java @@ -0,0 +1,45 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import ch.xxx.trader.domain.model.dto.CommonStatisticsDto; +import ch.xxx.trader.domain.model.dto.StatisticsCommon.CoinExchange; +import ch.xxx.trader.domain.model.dto.StatisticsCommon.StatisticsCurrPair; +import ch.xxx.trader.usecase.services.StatisticService; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/statistics") +public class StatisticsController { + private static final Logger LOGGER = LoggerFactory.getLogger(StatisticsController.class); + private final StatisticService statisticService; + + public StatisticsController(StatisticService statisticService) { + this.statisticService = statisticService; + } + + @GetMapping("/overview/{coinExchange}/{currPair}") + public Mono getOverview(@PathVariable StatisticsCurrPair currPair, @PathVariable CoinExchange coinExchange) { + return this.statisticService.getCommonStatistics(currPair, coinExchange); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/cron/PrepareDataTask.java b/backend/src/main/java/ch/xxx/trader/adapter/cron/PrepareDataTask.java new file mode 100644 index 00000000..ecc6551d --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/cron/PrepareDataTask.java @@ -0,0 +1,92 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.cron; + +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import ch.xxx.trader.usecase.services.BitfinexService; +import ch.xxx.trader.usecase.services.BitstampService; +import ch.xxx.trader.usecase.services.CoinbaseService; +import ch.xxx.trader.usecase.services.ItbitService; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import reactor.core.Disposable; + +@Component +public class PrepareDataTask { + private static final Logger LOG = LoggerFactory.getLogger(PrepareDataTask.class); + private final BitstampService bitstampService; + private final BitfinexService bitfinexService; + private final ItbitService itbitService; + private final CoinbaseService coinbaseService; + private Optional bitstampDisposableOpt = Optional.empty(); + private Optional bitfinexDisposableOpt = Optional.empty(); + private Optional itbitDisposableOpt = Optional.empty(); + private Optional coinbaseDisposableOpt = Optional.empty(); + + public PrepareDataTask(BitstampService bitstampService, BitfinexService bitfinexService, ItbitService itbitService, + CoinbaseService coinbaseService) { + this.bitstampService = bitstampService; + this.bitfinexService = bitfinexService; + this.itbitService = itbitService; + this.coinbaseService = coinbaseService; + } + + @Async + @Scheduled(cron = "0 5 0,12 ? * ?") + @SchedulerLock(name = "bitstamp_avg_scheduledTask", lockAtLeastFor = "PT10H", lockAtMostFor = "PT11H") + public void createBsAvg() { + this.bitstampDisposableOpt.ifPresent(myDisposable -> myDisposable.dispose()); + this.bitstampDisposableOpt = Optional.of(this.bitstampService.createBsAvg() + .doFinally(value -> BitstampService.singleInstanceLock = false).subscribe(result -> { + }, error -> LOG.warn("createBsAvg() failed.", error))); + } + + @Async + @Scheduled(cron = "0 45 0,12 ? * ?") + @SchedulerLock(name = "bitfinex_avg_scheduledTask", lockAtLeastFor = "PT10H", lockAtMostFor = "PT11H") + public void createBfAvg() { + this.bitfinexDisposableOpt.ifPresent(myDisposable -> myDisposable.dispose()); + this.bitfinexDisposableOpt = Optional.of(this.bitfinexService.createBfAvg() + .doFinally(value -> BitfinexService.singleInstanceLock = false).subscribe(result -> { + }, error -> LOG.warn("createBfAvg() failed.", error))); + } + + @Async + @Scheduled(cron = "0 25 1,13 ? * ?") + @SchedulerLock(name = "itbit_avg_scheduledTask", lockAtLeastFor = "PT10H", lockAtMostFor = "PT11H") + public void createIbAvg() { + this.itbitDisposableOpt.ifPresent(myDisposable -> myDisposable.dispose()); + this.itbitDisposableOpt = Optional.of(this.itbitService.createIbAvg() + .doFinally(value -> ItbitService.singleInstanceLock = false).subscribe(result -> { + }, error -> LOG.warn("createIbAvg() failed.", error))); + } + + @Async + @Scheduled(cron = "0 10 2,14 ? * ?") + @SchedulerLock(name = "coinbase_avg_scheduledTask", lockAtLeastFor = "PT10H", lockAtMostFor = "PT11H") + public void createCbAvg() { + this.coinbaseDisposableOpt.ifPresent(myDisposable -> myDisposable.dispose()); + this.coinbaseDisposableOpt = Optional.of(this.coinbaseService.createCbAvg() + .doFinally(value -> CoinbaseService.singleInstanceLock = false).subscribe(result -> { + }, error -> LOG.warn("createCbAvg() failed.", error))); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/cron/ScheduledTask.java b/backend/src/main/java/ch/xxx/trader/adapter/cron/ScheduledTask.java new file mode 100644 index 00000000..6366c6cb --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/cron/ScheduledTask.java @@ -0,0 +1,374 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.cron; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalTime; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +import ch.xxx.trader.domain.common.WebUtils; +import ch.xxx.trader.domain.model.dto.WrapperCb; +import ch.xxx.trader.domain.model.entity.QuoteBf; +import ch.xxx.trader.domain.model.entity.QuoteBs; +import ch.xxx.trader.domain.model.entity.QuoteCb; +import ch.xxx.trader.domain.model.entity.QuoteIb; +import ch.xxx.trader.domain.model.entity.paxos.PaxosQuote; +import ch.xxx.trader.domain.services.MyUserService; +import ch.xxx.trader.usecase.mappers.EventMapper; +import ch.xxx.trader.usecase.services.BitfinexService; +import ch.xxx.trader.usecase.services.BitstampService; +import ch.xxx.trader.usecase.services.CoinbaseService; +import ch.xxx.trader.usecase.services.ItbitService; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import reactor.core.Disposable; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +@Component +public class ScheduledTask { + private static final Logger LOG = LoggerFactory.getLogger(ScheduledTask.class); + + private static final String URLBS = "https://www.bitstamp.net/api"; + private static final String URLCB = "https://api.coinbase.com/v2"; + private static final String URLPA = "https://api.paxos.com/v2"; + private static final String URLBF = "https://api.bitfinex.com"; + + private final BitstampService bitstampService; + private final BitfinexService bitfinexService; + private final ItbitService itbitService; + private final CoinbaseService coinbaseService; + private final MyUserService myUserService; + private final WebClient.Builder webClientBuilder; + private final Map> disposables = new ConcurrentHashMap<>(); + private final Scheduler mongoImportScheduler = Schedulers.newBoundedElastic(20, 40, "mongoImport", 10); + + public ScheduledTask(BitstampService bitstampService, MyUserService myUserService, EventMapper messageMapper, + BitfinexService bitfinexService, ItbitService itbitService, CoinbaseService coinbaseService, WebClient.Builder webClientBuilder) { + this.bitstampService = bitstampService; + this.bitfinexService = bitfinexService; + this.itbitService = itbitService; + this.coinbaseService = coinbaseService; + this.myUserService = myUserService; + this.webClientBuilder = webClientBuilder; + } + +// @PostConstruct +// public void init() { +// +// } + + @Scheduled(fixedRate = 90000) + @SchedulerLock(name = "UpdateLoggedOutUsers_scheduledTask", lockAtLeastFor = "PT80S", lockAtMostFor = "PT85S") + @Order(1) + public void updateLoggedOutUsers() { + this.myUserService.updateLoggedOutUsers(); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 3000) + @SchedulerLock(name = "BitstampQuoteBTC_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteBTC() throws InterruptedException { + String currPair = "btceur"; + insertBsQuote(currPair); + } + + private void insertBsQuote(String currPair) { + // LOG.info(currPair); + this.disposeClient(currPair); + LocalTime start = LocalTime.now(); + final AtomicBoolean exceptionLogged = new AtomicBoolean(false); + Disposable subscribe = null; + try { + Mono request = this.webClientBuilder.build().get() + .uri(String.format("%s/v2/ticker/%s/", ScheduledTask.URLBS, currPair)) + .accept(MediaType.APPLICATION_JSON).exchangeToMono(response -> response.bodyToMono(QuoteBs.class)) + .map(res -> { + res.setPair(currPair); +// log.info(res.toString()); + return res; + }).timeout(Duration.ofSeconds(5L)).onErrorResume(ex -> { + exceptionLogged.set(this.logRequestFailed("Bitstamp", currPair, start, ex)); + return Mono.empty(); + }).subscribeOn(this.mongoImportScheduler); + subscribe = request.flatMap(myQuote -> this.bitstampService.insertQuote(Mono.just(myQuote)) + .timeout(Duration.ofSeconds(6L)).subscribeOn(this.mongoImportScheduler).onErrorResume(ex -> { + if (!exceptionLogged.get()) { + LOG.warn(String.format("Bitstamp data store failed for: %s", currPair), ex); + } + return Mono.empty(); + })).subscribeOn(this.mongoImportScheduler) + .subscribe(x -> this.logDuration("Bitstamp", currPair, start), + err -> LOG.warn(String.format("Bitstamp data import failed for: %s", currPair), err)); + } finally { + this.disposables.put(currPair, Optional.ofNullable(subscribe)); + } + } + + private void logDuration(String source, String currPair, LocalTime start) { + long durationInMs = Duration.between(start, LocalTime.now()).toMillis(); + if (durationInMs > 1000) { + LOG.info("Source: {} Duration of {}: {}ms", source, currPair, durationInMs); + } + } + + private boolean logRequestFailed(String source, String currPair, LocalTime start, Throwable ex) { + LOG.warn(String.format("%s data request for %s failed", source, currPair), ex); + return true; + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 6000) + @SchedulerLock(name = "BitstampQuoteETH_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteETH() throws InterruptedException { + String currPair = "etheur"; + this.insertBsQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 9000) + @SchedulerLock(name = "BitstampQuoteLTC_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteLTC() throws InterruptedException { + String currPair = "ltceur"; + this.insertBsQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 12000) + @SchedulerLock(name = "BitstampQuoteXRP_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteXRP() throws InterruptedException { + String currPair = "xrpeur"; + this.insertBsQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 15000) + @SchedulerLock(name = "CoinbaseQuote_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertCoinbaseQuote() { + final String currPair = "ALLUSD"; + // LOG.info(currPair); + this.disposeClient(currPair); + LocalTime start = LocalTime.now(); + final AtomicBoolean exceptionLogged = new AtomicBoolean(false); + Disposable subscribe = null; + try { + Mono request = this.webClientBuilder.build().get().uri(ScheduledTask.URLCB + "/exchange-rates?currency=BTC") + .accept(MediaType.APPLICATION_JSON).exchangeToMono(response -> { + return response.bodyToMono(WrapperCb.class); +// return response.bodyToMono(String.class); +// }).flatMap(value -> { +// // log.info(value); +// return Mono.just(this.messageMapper.mapJsonToObject(value, WrapperCb.class)); + }).flatMap(resp -> Mono.just(resp.getData())).flatMap(resp2 -> { +// log.info(resp2.getRates().toString()); + return Mono.just(resp2.getRates()); + }).timeout(Duration.ofSeconds(5L)).onErrorResume(ex -> { + exceptionLogged.set(this.logRequestFailed("Coinbase", currPair, start, ex)); + return Mono.empty(); + }).subscribeOn(this.mongoImportScheduler); + subscribe = request.flatMap(myQuote -> this.coinbaseService.insertQuote(Mono.just(myQuote)) + .timeout(Duration.ofSeconds(6L)).subscribeOn(this.mongoImportScheduler).onErrorResume(ex -> { + if (!exceptionLogged.get()) { + LOG.warn("Coinbase data store failed", ex); + } + return Mono.empty(); + })).subscribeOn(this.mongoImportScheduler) + .subscribe(x -> this.logDuration("Coinbase", currPair, start), + err -> LOG.warn("Coinbase data import failed.", err)); + } finally { + this.disposables.put(currPair, Optional.ofNullable(subscribe)); + } + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 21000) + @SchedulerLock(name = "ItbitUsdQuote_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertItbitUsdQuote() { + final String currPair = "BTCUSD"; + // LOG.info(currPair); + this.disposeClient(currPair); + LocalTime start = LocalTime.now(); + final AtomicBoolean exceptionLogged = new AtomicBoolean(false); + Disposable subscribe = null; + try { + Mono request = this.webClientBuilder.build().get() + .uri(String.format("%s/markets/%s/ticker", ScheduledTask.URLPA, currPair)) + .accept(MediaType.APPLICATION_JSON) + .exchangeToMono(response -> response.bodyToMono(PaxosQuote.class)).map(res -> { +// log.info(res.toString()); + return res; + }).map(paxosQuote -> this.convert(paxosQuote)).timeout(Duration.ofSeconds(5L)).onErrorResume(ex -> { + exceptionLogged.set(this.logRequestFailed("Ibit", currPair, start, ex)); + return Mono.empty(); + }).subscribeOn(this.mongoImportScheduler); + subscribe = request.flatMap(myQuote -> this.itbitService.insertQuote(Mono.just(myQuote)) + .timeout(Duration.ofSeconds(6L)).subscribeOn(this.mongoImportScheduler).onErrorResume(ex -> { + if (!exceptionLogged.get()) { + LOG.warn(String.format("Itbit data store failed for: %s", currPair), ex); + } + return Mono.empty(); + })).subscribeOn(this.mongoImportScheduler) + .subscribe(x -> this.logDuration("Itbit", currPair, start), + err -> LOG.warn(String.format("Itbit data import failed for: %s", currPair), err)); + } finally { + this.disposables.put(currPair, Optional.ofNullable(subscribe)); + } + } + + QuoteIb convert(PaxosQuote paxosQuote) { + final String currPair = "XBTUSD"; + QuoteIb quoteIb = new QuoteIb(currPair, new BigDecimal(paxosQuote.getBestBid().getPrice()), + new BigDecimal(paxosQuote.getBestBid().getAmount()), new BigDecimal(paxosQuote.getBestAsk().getPrice()), + new BigDecimal(paxosQuote.getBestAsk().getAmount()), + new BigDecimal(paxosQuote.getLastExecution().getPrice()), + new BigDecimal(paxosQuote.getLastExecution().getAmount()), + new BigDecimal(paxosQuote.getLastDay().getVolume()), new BigDecimal(paxosQuote.getToday().getVolume()), + new BigDecimal(paxosQuote.getLastDay().getHigh()), new BigDecimal(paxosQuote.getLastDay().getLow()), + new BigDecimal(paxosQuote.getToday().getOpen()), new BigDecimal(paxosQuote.getToday().getHigh()), + new BigDecimal(paxosQuote.getToday().getLow()), + new BigDecimal(paxosQuote.getToday().getVolumeWeightedAveragePrice()), + new BigDecimal(paxosQuote.getLastDay().getVolumeWeightedAveragePrice()), paxosQuote.getSnapshotAt()); + return quoteIb; + } + + private void disposeClient(final String currPair) { + Optional optional = this.disposables.getOrDefault(currPair, Optional.empty()); + optional.ifPresent(disposable -> disposable.dispose()); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 24000) + @SchedulerLock(name = "BitstampQuoteBTCUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteBTCUSD() throws InterruptedException { + String currPair = "btcusd"; + this.insertBsQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 27000) + @SchedulerLock(name = "BitstampQuoteETHUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteETHUSD() throws InterruptedException { + String currPair = "ethusd"; + this.insertBsQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 30000) + @SchedulerLock(name = "BitstampQuoteLTCUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteLTCUSD() throws InterruptedException { + String currPair = "ltcusd"; + this.insertBsQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 33000) + @SchedulerLock(name = "BitstampQuoteXRPUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitstampQuoteXRPUSD() throws InterruptedException { + String currPair = "xrpusd"; + this.insertBsQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 36000) + @SchedulerLock(name = "BitfinexQuoteBTCUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitfinexQuoteBTCUSD() throws InterruptedException { + String currPair = "btcusd"; + insertBfQuote(currPair); + } + + private void insertBfQuote(String currPair) { + // LOG.info(currPair); + this.disposeClient(currPair); + LocalTime start = LocalTime.now(); + final AtomicBoolean exceptionLogged = new AtomicBoolean(false); + Disposable subscribe = null; + try { + Mono request = this.webClientBuilder.build().get() + .uri(String.format("%s/v1/pubticker/%s", ScheduledTask.URLBF, currPair)) + .accept(MediaType.APPLICATION_JSON).exchangeToMono(response -> response.bodyToMono(QuoteBf.class)) + .map(res -> { + res.setPair(currPair); + QuoteBf result = checkBfTimestamp(res); +// log.info(res.toString()); + return result; + }).timeout(Duration.ofSeconds(5L)).onErrorResume(ex -> { + exceptionLogged.set(this.logRequestFailed("Bitfinex", currPair, start, ex)); + return Mono.empty(); + }).subscribeOn(this.mongoImportScheduler); + subscribe = request.flatMap(myQuote -> this.bitfinexService.insertQuote(Mono.just(myQuote)) + .timeout(Duration.ofSeconds(6L)).subscribeOn(this.mongoImportScheduler).onErrorResume(ex -> { + if (!exceptionLogged.get()) { + LOG.warn(String.format("Bitfinex data store failed for: %s", currPair), ex); + } + return Mono.empty(); + })).subscribeOn(this.mongoImportScheduler) + .subscribe(x -> this.logDuration("Bitfinex", currPair, start), + err -> LOG.warn(String.format("Bitfinex data import failed for: %s", currPair), err)); + } finally { + this.disposables.put(currPair, Optional.ofNullable(subscribe)); + } + } + + private QuoteBf checkBfTimestamp(QuoteBf res) { + QuoteBf result = res; + try { + BigDecimal timestamp = new BigDecimal(res.getTimestamp()); + LOG.debug(timestamp.toString()); + } catch (Exception e) { + LOG.warn(String.format("Failed to parse the timestamp: %s", res.getTimestamp()), e); + result = new QuoteBf(res.getMid(), res.getBid(), res.getAsk(), res.getLast_price(), res.getLow(), + res.getHigh(), res.getVolume(), "0.0"); + } + return result; + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 39000) + @SchedulerLock(name = "BitfinexQuoteETHUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitfinexQuoteETHUSD() throws InterruptedException { + String currPair = "ethusd"; + this.insertBfQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 42000) + @SchedulerLock(name = "BitfinexQuoteLTCUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitfinexQuoteLTCUSD() throws InterruptedException { + String currPair = "ltcusd"; + this.insertBfQuote(currPair); + } + + @Async("clientTaskExecutor") + @Scheduled(fixedRate = 60000, initialDelay = 45000) + @SchedulerLock(name = "BitfinexQuoteXRPUSD_scheduledTask", lockAtLeastFor = "PT50S", lockAtMostFor = "PT55S") + public void insertBitfinexQuoteXRPUSD() throws InterruptedException { + String currPair = "xrpusd"; + this.insertBfQuote(currPair); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/cron/TaskStarter.java b/backend/src/main/java/ch/xxx/trader/adapter/cron/TaskStarter.java new file mode 100644 index 00000000..830e1c36 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/cron/TaskStarter.java @@ -0,0 +1,64 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.cron; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import ch.xxx.trader.usecase.services.BitfinexService; +import ch.xxx.trader.usecase.services.BitstampService; +import ch.xxx.trader.usecase.services.CoinbaseService; +import ch.xxx.trader.usecase.services.ItbitService; + +@Component +public class TaskStarter { + private static final Logger log = LoggerFactory.getLogger(TaskStarter.class); + private final BitstampService bitstampService; + private final BitfinexService bitfinexService; + private final ItbitService itbitService; + private final CoinbaseService coinbaseService; + @Value("${single.instance.deployment:false}") + private boolean singleInstanceDeployment; + + public TaskStarter(BitstampService bitstampService, BitfinexService bitfinexService, ItbitService itbitService, + CoinbaseService coinbaseService) { + this.bitstampService = bitstampService; + this.bitfinexService = bitfinexService; + this.itbitService = itbitService; + this.coinbaseService = coinbaseService; + } + + @Async + @EventListener(ApplicationReadyEvent.class) + public void initAvgs() { + if (this.singleInstanceDeployment) { + log.info("ApplicationReady"); + this.bitstampService.createBsAvg().block(); + this.bitfinexService.createBfAvg().block(); + this.itbitService.createIbAvg().block(); + this.coinbaseService.createCbAvg().block(); + BitstampService.singleInstanceLock = false; + BitfinexService.singleInstanceLock = false; + ItbitService.singleInstanceLock = false; + CoinbaseService.singleInstanceLock = false; + } + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/events/EventConsumer.java b/backend/src/main/java/ch/xxx/trader/adapter/events/EventConsumer.java new file mode 100644 index 00000000..b1c23c2f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/events/EventConsumer.java @@ -0,0 +1,80 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.events; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.annotation.Profile; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.adapter.config.KafkaConfig; +import ch.xxx.trader.domain.model.dto.RevokedTokensDto; +import ch.xxx.trader.domain.model.entity.MyUser; +import ch.xxx.trader.usecase.mappers.EventMapper; +import ch.xxx.trader.usecase.services.MyUserServiceEvents; +import reactor.kafka.receiver.KafkaReceiver; +import reactor.kafka.receiver.ReceiverOptions; +import reactor.util.retry.Retry; + +@Profile("kafka | prod") +@Service +public class EventConsumer { + private static final Logger LOGGER = LoggerFactory.getLogger(EventConsumer.class); + private final ReceiverOptions receiverOptions; + private final KafkaReceiver userLogoutReceiver; + private final KafkaReceiver newUserReceiver; + private final MyUserServiceEvents myUserServiceEvents; + private final EventMapper eventMapper; + @Value("${spring.kafka.consumer.group-id}") + private String consumerGroupId; + + public EventConsumer(MyUserServiceEvents myUserServiceEvents, ReceiverOptions receiverOptions, + EventMapper eventMapper) { + this.receiverOptions = receiverOptions; + this.userLogoutReceiver = KafkaReceiver + .create(this.receiverOptions(List.of(KafkaConfig.USER_LOGOUT_SINK_TOPIC))); + this.newUserReceiver = KafkaReceiver.create(this.receiverOptions(List.of(KafkaConfig.NEW_USER_TOPIC))); + this.myUserServiceEvents = myUserServiceEvents; + this.eventMapper = eventMapper; + } + + private ReceiverOptions receiverOptions(Collection topics) { + return this.receiverOptions + .addAssignListener(p -> LOGGER.info("Group {} partitions assigned {}", this.consumerGroupId, p)) + .addRevokeListener(p -> LOGGER.info("Group {} partitions revoked {}", this.consumerGroupId, p)) + .subscription(topics); + } + + @EventListener(ApplicationReadyEvent.class) + public void doOnStartup() { + this.newUserReceiver.receiveAtmostOnce() + .doOnError(error -> LOGGER.error("Error receiving event, will retry", error)) + .retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofMinutes(1))) + .concatMap(myRecord -> this.myUserServiceEvents + .userSigninEvent(this.eventMapper.mapJsonToObject(myRecord.value(), MyUser.class))) + .subscribe(); + this.userLogoutReceiver.receiveAtmostOnce() + .doOnError(error -> LOGGER.error("Error receiving event, will retry", error)) + .retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofMinutes(1))) + .concatMap(myRecord -> this.myUserServiceEvents + .logoutEvent(this.eventMapper.mapJsonToObject(myRecord.value(), RevokedTokensDto.class))) + .subscribe(); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/events/EventProducer.java b/backend/src/main/java/ch/xxx/trader/adapter/events/EventProducer.java new file mode 100644 index 00000000..bde3ac54 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/events/EventProducer.java @@ -0,0 +1,61 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.events; + +import org.apache.kafka.clients.producer.ProducerRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.adapter.config.KafkaConfig; +import ch.xxx.trader.domain.model.entity.MyUser; +import ch.xxx.trader.domain.model.entity.RevokedToken; +import ch.xxx.trader.domain.services.MyEventProducer; +import ch.xxx.trader.usecase.mappers.EventMapper; +import reactor.core.publisher.Mono; +import reactor.kafka.sender.KafkaSender; + +@Profile("kafka | prod") +@Service +public class EventProducer implements MyEventProducer { + private static final Logger LOGGER = LoggerFactory.getLogger(EventProducer.class); + private final KafkaSender kafkaSender; + private final EventMapper eventMapper; + + public EventProducer(KafkaSender kafkaSender, EventMapper eventMapper) { + this.kafkaSender = kafkaSender; + this.eventMapper = eventMapper; + } + + public Mono sendNewUser(MyUser dto) { + String dtoJson = this.eventMapper.mapDtoToString(dto); + return this.kafkaSender.createOutbound() + .send(Mono.just(new ProducerRecord<>(KafkaConfig.NEW_USER_TOPIC, dto.getSalt(), dtoJson))) + .then() + .doOnError(e -> LOGGER.error( + String.format("Failed to send topic: %s value: %s", KafkaConfig.NEW_USER_TOPIC, dtoJson), e)) + .thenReturn(dto); + + } + + public Mono sendUserLogout(RevokedToken dto) { + String dtoJson = this.eventMapper.mapDtoToString(dto); + return this.kafkaSender.createOutbound() + .send(Mono.just(new ProducerRecord<>(KafkaConfig.USER_LOGOUT_SOURCE_TOPIC, dto.getName(), dtoJson))) + .then() + .doOnError(e -> LOGGER.error(String.format("Failed to send topic: %s value: %s", + KafkaConfig.USER_LOGOUT_SOURCE_TOPIC, dtoJson), e)) + .thenReturn(dto); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/events/KafkaStreams.java b/backend/src/main/java/ch/xxx/trader/adapter/events/KafkaStreams.java new file mode 100644 index 00000000..e15e2f4f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/events/KafkaStreams.java @@ -0,0 +1,94 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.events; + +import java.time.Duration; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +import org.apache.kafka.common.serialization.Serdes; +import org.apache.kafka.streams.StreamsBuilder; +import org.apache.kafka.streams.StreamsConfig; +import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.kstream.Consumed; +import org.apache.kafka.streams.kstream.Materialized; +import org.apache.kafka.streams.kstream.SlidingWindows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import ch.xxx.trader.adapter.config.KafkaConfig; +import ch.xxx.trader.domain.model.dto.RevokedTokensDto; +import ch.xxx.trader.domain.model.entity.RevokedToken; +import ch.xxx.trader.usecase.common.LastlogoutTimestampExtractor; + +@Profile("kafka | prod") +@Component +public class KafkaStreams { + private static final Logger LOGGER = LoggerFactory.getLogger(KafkaStreams.class); + private static final long LOGOUT_TIMEOUT = 120L; + private static final long GRACE_TIMEOUT = 5L; + private static final String UNPARSEABLE_JSON = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod"; + private ObjectMapper objectMapper; + + public KafkaStreams(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Bean("UserLogoutTopology") + public Topology userLogout(final StreamsBuilder builder) { + builder.stream(KafkaConfig.USER_LOGOUT_SOURCE_TOPIC, Consumed.with(Serdes.String(), Serdes.String())) + .groupByKey() + .windowedBy(SlidingWindows.ofTimeDifferenceAndGrace(Duration.ofSeconds(KafkaStreams.LOGOUT_TIMEOUT), + Duration.ofSeconds(KafkaStreams.GRACE_TIMEOUT))) + .aggregate(LinkedList::new, (key, value, myList) -> { +// LOGGER.info("Logout Stream value: {}", value); +// LOGGER.info("Logout Stream key: {}", key); + myList.add(value); +// LOGGER.info("Logout Stream myList: {}", myList.stream().collect(Collectors.joining(","))); + return myList; + }, Materialized.with(Serdes.String(), Serdes.ListSerde(LinkedList.class, Serdes.String()))).toStream() + .mapValues(value -> convertToRevokedTokens((List) value)) + .to(KafkaConfig.USER_LOGOUT_SINK_TOPIC); + Properties streamsConfiguration = new Properties(); + streamsConfiguration.put(StreamsConfig.DEFAULT_TIMESTAMP_EXTRACTOR_CLASS_CONFIG, + LastlogoutTimestampExtractor.class.getName()); + streamsConfiguration.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 1000L); + return builder.build(streamsConfiguration); + } + + private String convertToRevokedTokens(List value) { + try { + List revokedTokenList = value.stream().map(myValue -> { + RevokedToken result; + try { + result = this.objectMapper.readValue(myValue, RevokedToken.class); + } catch (Exception e) { + LOGGER.warn(String.format("Failed to deserialize %s", myValue), e); + result = new RevokedToken(null, UNPARSEABLE_JSON, UNPARSEABLE_JSON, null); + } + return result; + }).filter(myRevokedToken -> !UNPARSEABLE_JSON.equalsIgnoreCase(myRevokedToken.getName()) + && !UNPARSEABLE_JSON.equalsIgnoreCase(myRevokedToken.getUuid())).toList(); + return this.objectMapper.writeValueAsString(new RevokedTokensDto(revokedTokenList)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/backend/src/main/java/ch/xxx/trader/adapter/repository/ClientMongoRepository.java b/backend/src/main/java/ch/xxx/trader/adapter/repository/ClientMongoRepository.java new file mode 100644 index 00000000..4418b994 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/adapter/repository/ClientMongoRepository.java @@ -0,0 +1,100 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.adapter.repository; + +import java.util.Collection; + +import jakarta.validation.Valid; + +import org.bson.Document; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.ReactiveMongoOperations; +import org.springframework.data.mongodb.core.index.Index; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import com.mongodb.client.result.DeleteResult; +import com.mongodb.reactivestreams.client.MongoCollection; + +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Service +public class ClientMongoRepository implements MyMongoRepository { + private final ReactiveMongoOperations operations; + + public ClientMongoRepository(ReactiveMongoOperations operations) { + this.operations = operations; + } + + @Override + public Mono save(@Valid T objectToSave) { + return this.operations.save(objectToSave); + } + + @Override + public Mono findOne(Query query, Class entityClass) { + return this.operations.findOne(query, entityClass); + } + + @Override + public Mono findOne(Query query, Class entityClass, String name) { + return this.operations.findOne(query, entityClass, name); + } + + @Override + public Flux find(Query query, Class entityClass) { + return this.operations.find(query, entityClass); + } + + @Override + public Flux find(Query query, Class entityClass, String collectionName) { + return this.operations.find(query, entityClass, collectionName); + } + + @Override + public Flux insertAll(@Valid Mono> batchToSave, String collectionName) { + return this.operations.insertAll(batchToSave, collectionName); + } + + @Override + public Mono ensureIndex(String collectionName, String propertyName) { + Index myIndex = new Index(propertyName, Direction.DESC); + myIndex.named(collectionName + "-" + propertyName); + return this.operations.indexOps(collectionName).ensureIndex(myIndex); + } + + @Override + public Mono collectionExists(String collectionName) { + return this.operations.collectionExists(collectionName); + } + + @Override + public Mono> createCollection(String collectionName) { + return this.operations.createCollection(collectionName); + } + + @Override + public Mono insert(@Valid Mono quote) { + return this.operations.insert(quote); + } + + @Override + public Mono remove(Mono quote) { + return this.operations.remove(quote); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/common/JwtUtils.java b/backend/src/main/java/ch/xxx/trader/domain/common/JwtUtils.java new file mode 100644 index 00000000..3a947ce3 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/common/JwtUtils.java @@ -0,0 +1,94 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.common; + +import java.security.Key; +import java.util.Date; +import java.util.Map; +import java.util.Optional; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.http.HttpHeaders; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; + +public class JwtUtils { + public static final String AUTHORIZATION = HttpHeaders.AUTHORIZATION.toLowerCase(); + public static final String TOKENAUTHKEY = "auth"; + public static final String TOKENLASTMSGKEY = "lastmsg"; + public static final String BEARER = "Bearer "; + public static final String AUTHORITY = "authority"; + public static final String UUID = "uuid"; + public static final record TokenSubjectRole(String subject, String role) {} + + public static Optional extractToken(Map headers) { + String authStr = headers.get(AUTHORIZATION); + return extractToken(Optional.ofNullable(authStr)); + } + + private static Optional extractToken(Optional authStr) { + if (authStr.isPresent()) { + authStr = Optional.ofNullable(authStr.get().startsWith(BEARER) ? authStr.get().substring(7) : null); + } + return authStr; + } + + public static Optional resolveToken(String bearerToken) { + if (bearerToken != null && bearerToken.startsWith(JwtUtils.BEARER)) { + return Optional.of(bearerToken.substring(7, bearerToken.length())); + } + return Optional.empty(); + } + + public static Optional> getClaims(Optional token, Key jwtTokenKey) { + if (!token.isPresent()) { + return Optional.empty(); + } + return Optional.of(Jwts.parserBuilder().setSigningKey(jwtTokenKey).build().parseClaimsJws(token.get())); + } + + public static String getTokenRoles(Map headers, Key jwtTokenKey) { + Optional tokenStr = extractToken(headers); + Optional> claims = JwtUtils.getClaims(tokenStr, jwtTokenKey); + if (claims.isPresent() && new Date().before(claims.get().getBody().getExpiration())) { + return claims.get().getBody().get(TOKENAUTHKEY).toString(); + } + return ""; + } + + public static TokenSubjectRole getTokenUserRoles(Map headers, + Key jwtTokenKey) { + Optional tokenStr = extractToken(headers); + Optional> claims = JwtUtils.getClaims(tokenStr, jwtTokenKey); + if (claims.isPresent() && new Date().before(claims.get().getBody().getExpiration())) { + return new TokenSubjectRole(claims.get().getBody().getSubject(), + claims.get().getBody().get(TOKENAUTHKEY).toString()); + } + return new TokenSubjectRole(null, null); + } + + public static boolean checkToken(HttpServletRequest request, Key jwtTokenKey) { + Optional tokenStr = JwtUtils + .extractToken(Optional.ofNullable(request.getHeader(JwtUtils.AUTHORIZATION))); + Optional> claims = JwtUtils.getClaims(tokenStr, jwtTokenKey); + if (claims.isPresent() && new Date().before(claims.get().getBody().getExpiration()) + && claims.get().getBody().get(TOKENAUTHKEY).toString().contains(Role.USERS.name()) + && !claims.get().getBody().get(TOKENAUTHKEY).toString().contains(Role.GUEST.name())) { + return true; + } + return false; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/common/MongoUtils.java b/backend/src/main/java/ch/xxx/trader/domain/common/MongoUtils.java new file mode 100644 index 00000000..22dd25bb --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/common/MongoUtils.java @@ -0,0 +1,148 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.common; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; + +public class MongoUtils { + + public enum TimeFrame { + CURRENT("current"), TODAY("today"), SEVENDAYS("7days"), THIRTYDAYS("30days"), NINTYDAYS("90days"), + Month1("1month"), Month3("3month"), Month6("6month"), Year1("1year"), Year2("2year"), Year5("5year"); + + private TimeFrame(String value) { + this.value = value; + } + + private String value; + + public String getValue() { + return this.value; + } + }; + + public static final Map KEY_TO_TIMEFRAME = Collections.unmodifiableMap(new ConcurrentHashMap<>( + Stream.of(TimeFrame.values()).collect(Collectors.toMap(TimeFrame::getValue, tf -> tf)))); + + private static final Query buildQuery(Optional pair, boolean ascending, Optional begin, + int limit) { + limit = limit < 5000 ? limit : 5000; + Calendar cal = GregorianCalendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, -1); + Query query = new Query(); + query.allowDiskUse(true); + query.limit(limit); + pair.ifPresent(myValue -> query.addCriteria(Criteria.where("pair").is(myValue))); + begin.ifPresentOrElse(myValue -> query.addCriteria(Criteria.where("createdAt").gt(myValue.getTime())), + () -> query.addCriteria(Criteria.where("createdAt").gt(cal.getTime()))); + var myValue = ascending ? query.with(Sort.by("createdAt").ascending()) + : query.with(Sort.by("createdAt").descending()); + return query; + } + + private static final Query buildQuery(Optional pair, boolean ascending, Optional begin) { + return buildQuery(pair, ascending, begin, 1000); + } + + public static final Query build90DayQuery(Optional pair) { + Calendar cal = GregorianCalendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, -90); + return buildQuery(pair, true, Optional.of(cal)); + } + + public static final Query build30DayQuery(Optional pair) { + Calendar cal = GregorianCalendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, -30); + return buildQuery(pair, true, Optional.of(cal)); + } + + public static final Query build7DayQuery(Optional pair) { + Calendar cal = GregorianCalendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, -7); + return buildQuery(pair, true, Optional.of(cal)); + } + + public static final Query buildTimeFrameQuery(Optional pair, TimeFrame timeFrame, int limit) { + Calendar cal = GregorianCalendar.getInstance(); + Query query = switch (timeFrame) { + case CURRENT -> buildCurrentQuery(pair); + case TODAY -> buildTodayQuery(pair); + case SEVENDAYS -> build7DayQuery(pair); + case THIRTYDAYS -> build30DayQuery(pair); + case NINTYDAYS -> build90DayQuery(pair); + case Month1 -> { + cal.add(Calendar.MONTH, -1); + yield buildQuery(pair, true, Optional.of(cal)); + } + case Month3 -> { + cal.add(Calendar.MONTH, -3); + yield buildQuery(pair, true, Optional.of(cal)); + } + case Month6 -> { + cal.add(Calendar.MONTH, -6); + yield buildQuery(pair, true, Optional.of(cal)); + } + case Year1 -> { + cal.add(Calendar.YEAR, -1); + yield buildQuery(pair, true, Optional.of(cal)); + } + case Year2 -> { + cal.add(Calendar.YEAR, -2); + yield buildQuery(pair, true, Optional.of(cal), limit); + } + case Year5 -> { + cal.add(Calendar.YEAR, -5); + yield buildQuery(pair, true, Optional.of(cal), limit); + } + default -> new Query(); + }; + return query; + } + + public static final Query buildTimeFrameQuery(Optional pair, TimeFrame timeFrame) { + return buildTimeFrameQuery(pair, timeFrame, 1000); + } + + public static final Query buildTodayQuery(Optional pair) { + return buildQuery(pair, true, Optional.empty()); + } + + public static final Query buildCurrentQuery(Optional pair) { + return buildQuery(pair, false, Optional.empty()); + } + + public static final boolean filterEvenMinutes(Date date) { + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).getMinute() % 2 == 0; + } + + public static final boolean filter10Minutes(Date date) { + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).getMinute() % 10 == 0; + } +} diff --git a/src/main/java/ch/xxx/trader/PasswordEncryption.java b/backend/src/main/java/ch/xxx/trader/domain/common/PasswordEncryption.java similarity index 67% rename from src/main/java/ch/xxx/trader/PasswordEncryption.java rename to backend/src/main/java/ch/xxx/trader/domain/common/PasswordEncryption.java index ebc83a7b..669200a3 100644 --- a/src/main/java/ch/xxx/trader/PasswordEncryption.java +++ b/backend/src/main/java/ch/xxx/trader/domain/common/PasswordEncryption.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader; +package ch.xxx.trader.domain.common; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -31,27 +31,15 @@ public class PasswordEncryption { public boolean authenticate(String attemptedPassword, String encryptedPassword, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException { - // Encrypt the clear-text password using the same salt that was used to - // encrypt the original password String encryptedAttemptedPassword = getEncryptedPassword(attemptedPassword, salt); - // Authentication succeeds if encrypted password that the user entered - // is equal to the stored hash return encryptedPassword.equals(encryptedAttemptedPassword); } public String getEncryptedPassword(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException { - // PBKDF2 with SHA-1 as the hashing algorithm. Note that the NIST - // specifically names SHA-1 as an acceptable hashing algorithm for PBKDF2 - String algorithm = "PBKDF2WithHmacSHA1"; - // SHA-1 generates 160 bit hashes, so that's what makes sense here - int derivedKeyLength = 160; - // Pick an iteration count that works for you. The NIST recommends at - // least 1,000 iterations: - // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf - // iOS 4.x reportedly uses 10,000: - // http://blog.crackpassword.com/2010/09/smartphone-forensics-cracking-blackberry-backup-passwords/ + String algorithm = "PBKDF2WithHmacSHA256"; + int derivedKeyLength = 256; int iterations = 20000; char[] pwd = new String(password).toCharArray(); KeySpec spec = new PBEKeySpec(pwd, Base64.getDecoder().decode(salt), iterations, derivedKeyLength); @@ -61,9 +49,7 @@ public String getEncryptedPassword(String password, String salt) } public String generateSalt() throws NoSuchAlgorithmException { - // VERY important to use SecureRandom instead of just Random SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); - // Generate a 8 byte (64 bit) salt as recommended by RSA PKCS5 byte[] salt = new byte[8]; random.nextBytes(salt); return Base64.getEncoder().encodeToString(salt); diff --git a/src/angular/trader/src/app/common/quoteBf.ts b/backend/src/main/java/ch/xxx/trader/domain/common/Role.java similarity index 71% rename from src/angular/trader/src/app/common/quoteBf.ts rename to backend/src/main/java/ch/xxx/trader/domain/common/Role.java index a97412bc..ec7bd012 100644 --- a/src/angular/trader/src/app/common/quoteBf.ts +++ b/backend/src/main/java/ch/xxx/trader/domain/common/Role.java @@ -13,16 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -export interface QuoteBf { - _id: string; - pair: string; - createdAt: Date; - mid: number; - bid: number; - ask: number; - last_price: number; - low: number; - high: number; - volume: number; - timestamp: string; -} \ No newline at end of file +package ch.xxx.trader.domain.common; + +import org.springframework.security.core.GrantedAuthority; + +public enum Role implements GrantedAuthority{ + USERS, GUEST; + + @Override + public String getAuthority() { + return this.name(); + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/common/StreamHelpers.java b/backend/src/main/java/ch/xxx/trader/domain/common/StreamHelpers.java new file mode 100644 index 00000000..50acd0f5 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/common/StreamHelpers.java @@ -0,0 +1,51 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.common; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class StreamHelpers { + public static Predicate distinctByKey( + Function keyExtractor) { + + Map seen = new ConcurrentHashMap<>(); + return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + } + + public static Stream toStream(Collection collection) { + return Optional.ofNullable(collection).stream().flatMap(myList -> myList.stream()); + } + + public static Stream toStream(T[] array) { + return Optional.ofNullable(array).stream().flatMap(myArray -> List.of(array).stream()); + } + + public static Stream toStream(T object) { + return Optional.ofNullable(object).stream(); + } + + public static Stream unboxOptionals(Stream> optSteam) { + return optSteam.filter(Optional::isPresent).map(Optional::get); + } + + public static Stream unboxOptionals(Optional opt) { + return opt.stream(); + } +} diff --git a/src/main/java/ch/xxx/trader/WebUtils.java b/backend/src/main/java/ch/xxx/trader/domain/common/WebUtils.java similarity index 64% rename from src/main/java/ch/xxx/trader/WebUtils.java rename to backend/src/main/java/ch/xxx/trader/domain/common/WebUtils.java index 52c6109f..65d89e87 100644 --- a/src/main/java/ch/xxx/trader/WebUtils.java +++ b/backend/src/main/java/ch/xxx/trader/domain/common/WebUtils.java @@ -1,14 +1,14 @@ -package ch.xxx.trader; +package ch.xxx.trader.domain.common; import java.time.Duration; import java.time.Instant; - -import javax.servlet.http.HttpServletRequest; +import java.util.Map; +import java.util.Optional; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; -import io.netty.channel.ChannelOption; +import jakarta.servlet.http.HttpServletRequest; public class WebUtils { @@ -16,6 +16,7 @@ public class WebUtils { public static final String LASTOBCALLBS = "LAST_ORDERBOOK_CALL_BITSTAMP"; public static final String LASTOBCALLIB = "LAST_ORDERBOOK_CALL_ITBIT"; public static final String SECURITYCONTEXT = "SPRING_SECURITY_CONTEXT"; + public static final String AUTHORIZATION = "authorization"; public static boolean checkOBRequest(HttpServletRequest request, String sessionKey) { Instant last = (Instant) request.getSession().getAttribute(sessionKey); @@ -30,9 +31,19 @@ public static boolean checkOBRequest(HttpServletRequest request, String sessionK } public static WebClient buildWebClient(String url) { - ReactorClientHttpConnector connector = new ReactorClientHttpConnector( - options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)); + ReactorClientHttpConnector connector = new ReactorClientHttpConnector(); return WebClient.builder().clientConnector(connector).baseUrl(url).build(); } - + + public static Optional extractToken(Map headers) { + String authStr = headers.get(AUTHORIZATION); + return extractToken(Optional.ofNullable(authStr)); + } + + private static Optional extractToken(Optional authStr) { + if(authStr.isPresent()) { + authStr = Optional.ofNullable(authStr.get().startsWith("Bearer ") ? authStr.get().substring(7) : null); + } + return authStr; + } } diff --git a/backend/src/main/java/ch/xxx/trader/domain/exceptions/AuthenticationException.java b/backend/src/main/java/ch/xxx/trader/domain/exceptions/AuthenticationException.java new file mode 100644 index 00000000..f53a93de --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/exceptions/AuthenticationException.java @@ -0,0 +1,26 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.exceptions; + +public class AuthenticationException extends RuntimeException { + + private static final long serialVersionUID = -4778173207515812187L; + + public AuthenticationException(String message) { + super(message); + } + + public AuthenticationException(String message, Throwable th) { + super(message, th); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/exceptions/JwtTokenValidationException.java b/backend/src/main/java/ch/xxx/trader/domain/exceptions/JwtTokenValidationException.java new file mode 100644 index 00000000..58644627 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/exceptions/JwtTokenValidationException.java @@ -0,0 +1,29 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.exceptions; + +public class JwtTokenValidationException extends RuntimeException { + + private static final long serialVersionUID = -1119114699758662609L; + + public JwtTokenValidationException(String message) { + super(message); + } + + public JwtTokenValidationException(String message, Throwable th) { + super(message, th); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/exceptions/MyErrorAttributes.java b/backend/src/main/java/ch/xxx/trader/domain/exceptions/MyErrorAttributes.java new file mode 100644 index 00000000..715ca1cb --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/exceptions/MyErrorAttributes.java @@ -0,0 +1,24 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.exceptions; + +import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; +import org.springframework.stereotype.Component; + +@Component +public class MyErrorAttributes extends DefaultErrorAttributes { + +} diff --git a/src/main/java/ch/xxx/trader/dtos/AuthCheck.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/AuthCheck.java similarity index 94% rename from src/main/java/ch/xxx/trader/dtos/AuthCheck.java rename to backend/src/main/java/ch/xxx/trader/domain/model/dto/AuthCheck.java index 92429e4d..6e9027a9 100644 --- a/src/main/java/ch/xxx/trader/dtos/AuthCheck.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/AuthCheck.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.dto; import java.util.Date; @@ -24,7 +24,7 @@ public class AuthCheck { private final Date createdAt = new Date(); private final String hash; private final String path; - private final boolean authorized; + private final boolean authorized; public AuthCheck(@JsonProperty("hash") String hash,@JsonProperty("path") String path, @JsonProperty("authorized") boolean authorized) { super(); diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/dto/CommonStatisticsDto.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/CommonStatisticsDto.java new file mode 100644 index 00000000..fbb90228 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/CommonStatisticsDto.java @@ -0,0 +1,202 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.dto; + +import java.math.BigDecimal; + +import com.fasterxml.jackson.annotation.JsonRootName; + +import ch.xxx.trader.domain.model.dto.StatisticsCommon.StatisticsCurrPair; + +@JsonRootName(value = "CommonStatistics") +public class CommonStatisticsDto { + private StatisticsCurrPair currPair; + private Double performance1Month; + private Double performance3Month; + private Double performance6Month; + private Double performance1Year; + private Double performance2Year; + private Double performance5Year; + private BigDecimal volatility1Month; + private BigDecimal volatility3Month; + private BigDecimal volatility6Month; + private BigDecimal volatility1Year; + private BigDecimal volatility2Year; + private BigDecimal volatility5Year; + private BigDecimal avgVolume1Month; + private BigDecimal avgVolume3Month; + private BigDecimal avgVolume6Month; + private BigDecimal avgVolume1Year; + private BigDecimal avgVolume2Year; + private BigDecimal avgVolume5Year; + private RangeDto range1Month = new RangeDto(); + private RangeDto range3Month = new RangeDto(); + private RangeDto range6Month = new RangeDto(); + private RangeDto range1Year = new RangeDto(); + private RangeDto range2Year = new RangeDto(); + private RangeDto range5Year = new RangeDto(); + + public Double getPerformance1Month() { + return performance1Month; + } + public void setPerformance1Month(Double performance1Month) { + this.performance1Month = performance1Month; + } + public Double getPerformance3Month() { + return performance3Month; + } + public void setPerformance3Month(Double performance3Month) { + this.performance3Month = performance3Month; + } + public Double getPerformance6Month() { + return performance6Month; + } + public void setPerformance6Month(Double performance6Month) { + this.performance6Month = performance6Month; + } + public Double getPerformance1Year() { + return performance1Year; + } + public void setPerformance1Year(Double performance1Year) { + this.performance1Year = performance1Year; + } + public Double getPerformance2Year() { + return performance2Year; + } + public void setPerformance2Year(Double performance2Year) { + this.performance2Year = performance2Year; + } + public Double getPerformance5Year() { + return performance5Year; + } + public void setPerformance5Year(Double performance5Year) { + this.performance5Year = performance5Year; + } + public BigDecimal getVolatility1Month() { + return volatility1Month; + } + public void setVolatility1Month(BigDecimal volatility1Month) { + this.volatility1Month = volatility1Month; + } + public BigDecimal getVolatility3Month() { + return volatility3Month; + } + public void setVolatility3Month(BigDecimal volatility3Month) { + this.volatility3Month = volatility3Month; + } + public BigDecimal getVolatility6Month() { + return volatility6Month; + } + public void setVolatility6Month(BigDecimal volatility6Month) { + this.volatility6Month = volatility6Month; + } + public BigDecimal getVolatility1Year() { + return volatility1Year; + } + public void setVolatility1Year(BigDecimal volatility1Year) { + this.volatility1Year = volatility1Year; + } + public BigDecimal getVolatility2Year() { + return volatility2Year; + } + public void setVolatility2Year(BigDecimal volatility2Year) { + this.volatility2Year = volatility2Year; + } + public BigDecimal getVolatility5Year() { + return volatility5Year; + } + public void setVolatility5Year(BigDecimal volatility5Year) { + this.volatility5Year = volatility5Year; + } + public BigDecimal getAvgVolume1Month() { + return avgVolume1Month; + } + public void setAvgVolume1Month(BigDecimal avgVolume1Month) { + this.avgVolume1Month = avgVolume1Month; + } + public BigDecimal getAvgVolume3Month() { + return avgVolume3Month; + } + public void setAvgVolume3Month(BigDecimal avgVolume3Month) { + this.avgVolume3Month = avgVolume3Month; + } + public BigDecimal getAvgVolume6Month() { + return avgVolume6Month; + } + public void setAvgVolume6Month(BigDecimal avgVolume6Month) { + this.avgVolume6Month = avgVolume6Month; + } + public BigDecimal getAvgVolume1Year() { + return avgVolume1Year; + } + public void setAvgVolume1Year(BigDecimal avgVolume1Year) { + this.avgVolume1Year = avgVolume1Year; + } + public BigDecimal getAvgVolume2Year() { + return avgVolume2Year; + } + public void setAvgVolume2Year(BigDecimal avgVolume2Year) { + this.avgVolume2Year = avgVolume2Year; + } + public BigDecimal getAvgVolume5Year() { + return avgVolume5Year; + } + public void setAvgVolume5Year(BigDecimal avgVolume5Year) { + this.avgVolume5Year = avgVolume5Year; + } + public RangeDto getRange1Month() { + return range1Month; + } + public void setRange1Month(RangeDto range1Month) { + this.range1Month = range1Month; + } + public RangeDto getRange3Month() { + return range3Month; + } + public void setRange3Month(RangeDto range3Month) { + this.range3Month = range3Month; + } + public RangeDto getRange6Month() { + return range6Month; + } + public void setRange6Month(RangeDto range6Month) { + this.range6Month = range6Month; + } + public RangeDto getRange1Year() { + return range1Year; + } + public void setRange1Year(RangeDto range1Year) { + this.range1Year = range1Year; + } + public RangeDto getRange2Year() { + return range2Year; + } + public void setRange2Year(RangeDto range2Year) { + this.range2Year = range2Year; + } + public RangeDto getRange5Year() { + return range5Year; + } + public void setRange5Year(RangeDto range5Year) { + this.range5Year = range5Year; + } + public StatisticsCurrPair getCurrPair() { + return currPair; + } + public void setCurrPair(StatisticsCurrPair currPair) { + this.currPair = currPair; + } +} diff --git a/src/main/java/ch/xxx/trader/dtos/CurrencyCb.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/CurrencyCb.java similarity index 91% rename from src/main/java/ch/xxx/trader/dtos/CurrencyCb.java rename to backend/src/main/java/ch/xxx/trader/domain/model/dto/CurrencyCb.java index 576b16d7..fb115a8e 100644 --- a/src/main/java/ch/xxx/trader/dtos/CurrencyCb.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/CurrencyCb.java @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; - -import java.util.List; +package ch.xxx.trader.domain.model.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import ch.xxx.trader.domain.model.entity.QuoteCb; + public class CurrencyCb { private final String currency; private final QuoteCb rates; diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/dto/QuotePdf.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/QuotePdf.java new file mode 100644 index 00000000..fee565bf --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/QuotePdf.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.dto; + +import java.math.BigDecimal; +import java.util.Date; + +public class QuotePdf { + private BigDecimal last; + private String pair; + private BigDecimal volume; + private Date timestamp; + private BigDecimal bid; + private BigDecimal ask; + + public QuotePdf() { + + } + + public QuotePdf(BigDecimal last, String pair, BigDecimal volume, Date timestamp, BigDecimal bid, BigDecimal ask) { + super(); + this.last = last; + this.pair = pair; + this.volume = volume; + this.timestamp = timestamp; + this.bid = bid; + this.ask = ask; + } + public BigDecimal getLast() { + return last; + } + public void setLast(BigDecimal last) { + this.last = last; + } + public String getPair() { + return pair; + } + public void setPair(String pair) { + this.pair = pair; + } + public BigDecimal getVolume() { + return volume; + } + public void setVolume(BigDecimal volume) { + this.volume = volume; + } + public Date getTimestamp() { + return timestamp; + } + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + public BigDecimal getBid() { + return bid; + } + public void setBid(BigDecimal bid) { + this.bid = bid; + } + public BigDecimal getAsk() { + return ask; + } + public void setAsk(BigDecimal ask) { + this.ask = ask; + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/dto/RangeDto.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/RangeDto.java new file mode 100644 index 00000000..398fd676 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/RangeDto.java @@ -0,0 +1,47 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.dto; + +import java.math.BigDecimal; + +public class RangeDto { + private BigDecimal min = BigDecimal.ZERO; + private BigDecimal max = BigDecimal.ZERO; + + public RangeDto() {} + + public RangeDto(BigDecimal min, BigDecimal max) { + this.min = min; + this.max = max; + } + + public BigDecimal getMin() { + return min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public void setMax(BigDecimal max) { + this.max = max; + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/dto/RefreshTokenDto.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/RefreshTokenDto.java new file mode 100644 index 00000000..ecd6ba79 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/RefreshTokenDto.java @@ -0,0 +1,31 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.dto; + +public class RefreshTokenDto { + private String refreshToken; + + public RefreshTokenDto(String refreshToken) { + super(); + this.refreshToken = refreshToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/dto/RevokedTokensDto.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/RevokedTokensDto.java new file mode 100644 index 00000000..430dc219 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/RevokedTokensDto.java @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.dto; + +import java.util.LinkedList; +import java.util.List; + +import ch.xxx.trader.domain.model.entity.RevokedToken; + +public class RevokedTokensDto { + private List revokedTokens = new LinkedList<>(); + + public RevokedTokensDto() { + } + + public RevokedTokensDto(List revokedTokens) { + super(); + this.revokedTokens = revokedTokens; + } + + public List getRevokedTokens() { + return revokedTokens; + } + + public void setRevokedTokens(List revokedTokens) { + this.revokedTokens = revokedTokens; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/dto/StatisticsCommon.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/StatisticsCommon.java new file mode 100644 index 00000000..25a93357 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/StatisticsCommon.java @@ -0,0 +1,42 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.dto; + +public class StatisticsCommon { + public static enum StatisticsCurrPair { + BcUsd("btcusd","btcusd"), EthUsd("ethusd","ethusd"), LcUsd("ltcusd","ltcusd"), RpUsd("xrpusd","xrpusd"); + + private String bitStampKey; + private String bitfinexKey; + + StatisticsCurrPair(String bitStampKey, String bitfinexKey) { + this.bitStampKey = bitStampKey; + this.bitfinexKey = bitfinexKey; + } + + public String getBitStampKey() { + return bitStampKey; + } + + public String getBitfinexKey() { + return bitfinexKey; + } + } + + public enum CoinExchange { + Bitfinex, Bitstamp + } +} diff --git a/src/main/java/ch/xxx/trader/dtos/WrapperCb.java b/backend/src/main/java/ch/xxx/trader/domain/model/dto/WrapperCb.java similarity index 95% rename from src/main/java/ch/xxx/trader/dtos/WrapperCb.java rename to backend/src/main/java/ch/xxx/trader/domain/model/dto/WrapperCb.java index 1254747e..a8eebd6f 100644 --- a/src/main/java/ch/xxx/trader/dtos/WrapperCb.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/dto/WrapperCb.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/entity/MyMongoRepository.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/MyMongoRepository.java new file mode 100644 index 00000000..34fa5edd --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/MyMongoRepository.java @@ -0,0 +1,52 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.entity; + +import java.util.Collection; + +import org.bson.Document; +import org.springframework.data.mongodb.core.query.Query; + +import com.mongodb.client.result.DeleteResult; +import com.mongodb.reactivestreams.client.MongoCollection; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface MyMongoRepository { + + Mono findOne(Query query, Class entityClass); + + Mono findOne(Query query, Class entityClass, String name); + + Flux find(Query query, Class entityClass); + + Flux find(Query query, Class entityClass, String collectionName); + + Flux insertAll(Mono> batchToSave, String collectionName); + + Mono insert(Mono quote); + + Mono collectionExists(String collectionName); + + Mono> createCollection(String collectionName); + + Mono save(T objectToSave); + + Mono remove(Mono quote); + + Mono ensureIndex(String collectionName, String propertyName); +} diff --git a/src/main/java/ch/xxx/trader/dtos/MyUser.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/MyUser.java similarity index 54% rename from src/main/java/ch/xxx/trader/dtos/MyUser.java rename to backend/src/main/java/ch/xxx/trader/domain/model/entity/MyUser.java index 1342488a..6cc44232 100644 --- a/src/main/java/ch/xxx/trader/dtos/MyUser.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/MyUser.java @@ -13,18 +13,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.entity; import java.util.Arrays; import java.util.Collection; import java.util.Date; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @Document @@ -34,16 +40,31 @@ public class MyUser implements UserDetails { @Id private ObjectId _id; + @NotNull @JsonProperty - private final Date createdAt = new Date(); + private Date createdAt = new Date(); + @NotBlank + @Size(min = 4) @JsonProperty private String userId; + @NotBlank + @Size(min = 4) @JsonProperty private String password; @JsonProperty private String salt; @JsonProperty - private String email; + private String email; + @JsonProperty + private String token; + @JsonProperty + private boolean accountNonExpired = true; + @JsonProperty + private boolean accountNonLocked = true; + @JsonProperty + private boolean credentialsNonExpired = true; + @JsonProperty + private boolean enabled = true; public ObjectId get_id() { return _id; @@ -75,32 +96,63 @@ public String getSalt() { public void setSalt(String salt) { this.salt = salt; } + @JsonIgnore @Override public Collection getAuthorities() { - GrantedAuthority auth = () -> "USERS"; + GrantedAuthority auth = new SimpleGrantedAuthority("USERS"); return Arrays.asList(auth); } + @JsonIgnore @Override public String getUsername() { return this.userId; } @Override public boolean isAccountNonExpired() { - return true; + return this.accountNonExpired; } @Override public boolean isAccountNonLocked() { - return true; + return this.accountNonLocked; } @Override public boolean isCredentialsNonExpired() { - return true; + return this.credentialsNonExpired; } @Override public boolean isEnabled() { - return true; + return this.enabled; } public Date getCreatedAt() { return createdAt; } + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + public String getToken() { + return token; + } + public void setToken(String token) { + this.token = token; + } + public void setAccountNonExpired(boolean accountNonExpired) { + this.accountNonExpired = accountNonExpired; + } + public void setAccountNonLocked(boolean accountNonLocked) { + this.accountNonLocked = accountNonLocked; + } + public void setCredentialsNonExpired(boolean credentialsNonExpired) { + this.credentialsNonExpired = credentialsNonExpired; + } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + return "MyUser [_id=" + _id + ", createdAt=" + createdAt + ", userId=" + userId + ", password=" + password + + ", salt=" + salt + ", email=" + email + ", token=" + token + ", accountNonExpired=" + + accountNonExpired + ", accountNonLocked=" + accountNonLocked + ", credentialsNonExpired=" + + credentialsNonExpired + ", enabled=" + enabled + "]"; + } } diff --git a/src/main/java/ch/xxx/trader/dtos/Quote.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/Quote.java similarity index 63% rename from src/main/java/ch/xxx/trader/dtos/Quote.java rename to backend/src/main/java/ch/xxx/trader/domain/model/entity/Quote.java index f2328a1e..b38909c2 100644 --- a/src/main/java/ch/xxx/trader/dtos/Quote.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/Quote.java @@ -1,4 +1,4 @@ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.entity; import java.util.Date; diff --git a/src/main/java/ch/xxx/trader/dtos/QuoteBf.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteBf.java similarity index 91% rename from src/main/java/ch/xxx/trader/dtos/QuoteBf.java rename to backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteBf.java index 0dd9d791..e2a4f268 100644 --- a/src/main/java/ch/xxx/trader/dtos/QuoteBf.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteBf.java @@ -13,13 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.entity; import java.math.BigDecimal; import java.util.Date; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import com.fasterxml.jackson.annotation.JsonProperty; @@ -29,8 +33,12 @@ public class QuoteBf implements Quote { @Id private ObjectId _id; + @NotBlank + @Indexed() @JsonProperty private String pair; + @NotNull + @Indexed(name = "QuoteBf-createdAt") @JsonProperty private Date createdAt = new Date(); diff --git a/src/main/java/ch/xxx/trader/dtos/QuoteBs.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteBs.java similarity index 91% rename from src/main/java/ch/xxx/trader/dtos/QuoteBs.java rename to backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteBs.java index ddee86e9..e81691a5 100644 --- a/src/main/java/ch/xxx/trader/dtos/QuoteBs.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteBs.java @@ -13,13 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.entity; import java.math.BigDecimal; import java.util.Date; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import com.fasterxml.jackson.annotation.JsonProperty; @@ -29,8 +33,12 @@ public class QuoteBs implements Quote { @Id private ObjectId _id; + @NotBlank + @Indexed @JsonProperty private String pair; + @NotNull + @Indexed(name = "QuoteBs-createdAt") @JsonProperty private Date createdAt = new Date(); private final BigDecimal high; diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteCb.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteCb.java new file mode 100644 index 00000000..57e26ad3 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteCb.java @@ -0,0 +1,3933 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.entity; + +import java.math.BigDecimal; +import java.util.Date; + +import jakarta.validation.constraints.NotNull; + +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Document +public class QuoteCb implements Quote { + + @Id + private ObjectId _id; + @NotNull + @Indexed(name = "QuoteCb-createdAt") + @JsonProperty + private Date createdAt = new Date(); + @JsonProperty("XCN") + private BigDecimal xcn = BigDecimal.ZERO; + @JsonProperty("METIS") + private BigDecimal metis = BigDecimal.ZERO; + @JsonProperty("BOBA") + private BigDecimal boba = BigDecimal.ZERO; + @JsonProperty("MONA") + private BigDecimal mona = BigDecimal.ZERO; + @JsonProperty("MEDIA") + private BigDecimal media = BigDecimal.ZERO; + @JsonProperty("GUSD") + private BigDecimal gusd = BigDecimal.ZERO; + @JsonProperty("AST") + private BigDecimal ast = BigDecimal.ZERO; + @JsonProperty("DYP") + private BigDecimal dyp = BigDecimal.ZERO; + @JsonProperty("POND") + private BigDecimal pond = BigDecimal.ZERO; + @JsonProperty("TIME") + private BigDecimal time = BigDecimal.ZERO; + @JsonProperty("DAR") + private BigDecimal dar = BigDecimal.ZERO; + @JsonProperty("C98") + private BigDecimal c98 = BigDecimal.ZERO; + @JsonProperty("BIT") + private BigDecimal bit = BigDecimal.ZERO; + @JsonProperty("FIS") + private BigDecimal fis = BigDecimal.ZERO; + @JsonProperty("DEXT") + private BigDecimal dext = BigDecimal.ZERO; + @JsonProperty("ALEPH") + private BigDecimal aleph = BigDecimal.ZERO; + @JsonProperty("FORT") + private BigDecimal fort = BigDecimal.ZERO; + @JsonProperty("ELA") + private BigDecimal ela = BigDecimal.ZERO; + @JsonProperty("DREP") + private BigDecimal drep = BigDecimal.ZERO; + @JsonProperty("MUSE") + private BigDecimal muse = BigDecimal.ZERO; + @JsonProperty("INDEX") + private BigDecimal index = BigDecimal.ZERO; + @JsonProperty("WAMPL") + private BigDecimal wampl = BigDecimal.ZERO; + @JsonProperty("MATH") + private BigDecimal math = BigDecimal.ZERO; + @JsonProperty("JUP") + private BigDecimal jup = BigDecimal.ZERO; + @JsonProperty("HOPR") + private BigDecimal hopr = BigDecimal.ZERO; + @JsonProperty("AED") + private BigDecimal aed = BigDecimal.ZERO; + @JsonProperty("PRQ") + private BigDecimal prq = BigDecimal.ZERO; + @JsonProperty("ATA") + private BigDecimal ata = BigDecimal.ZERO; + @JsonProperty("AFN") + private BigDecimal afn = BigDecimal.ZERO; + @JsonProperty("ALL") + private BigDecimal all = BigDecimal.ZERO; + @JsonProperty("AMD") + private BigDecimal amd = BigDecimal.ZERO; + @JsonProperty("ANG") + private BigDecimal ang = BigDecimal.ZERO; + @JsonProperty("AOA") + private BigDecimal aoa = BigDecimal.ZERO; + @JsonProperty("ARS") + private BigDecimal ars = BigDecimal.ZERO; + @JsonProperty("AUD") + private BigDecimal aud = BigDecimal.ZERO; + @JsonProperty("AWG") + private BigDecimal awg = BigDecimal.ZERO; + @JsonProperty("AZN") + private BigDecimal azn = BigDecimal.ZERO; + @JsonProperty("BAM") + private BigDecimal bam = BigDecimal.ZERO; + @JsonProperty("BBD") + private BigDecimal bbd = BigDecimal.ZERO; + @JsonProperty("BDT") + private BigDecimal bdt = BigDecimal.ZERO; + @JsonProperty("BGN") + private BigDecimal bgn = BigDecimal.ZERO; + @JsonProperty("BHD") + private BigDecimal bhd = BigDecimal.ZERO; + @JsonProperty("BIF") + private BigDecimal bif = BigDecimal.ZERO; + @JsonProperty("BMD") + private BigDecimal bmd = BigDecimal.ZERO; + @JsonProperty("BND") + private BigDecimal bnd = BigDecimal.ZERO; + @JsonProperty("BOB") + private BigDecimal bob = BigDecimal.ZERO; + @JsonProperty("BRL") + private BigDecimal brl = BigDecimal.ZERO; + @JsonProperty("BSD") + private BigDecimal bsd = BigDecimal.ZERO; + @JsonProperty("BTC") + private BigDecimal btc = BigDecimal.ZERO; + @JsonProperty("BTN") + private BigDecimal btn = BigDecimal.ZERO; + @JsonProperty("BWP") + private BigDecimal bwp = BigDecimal.ZERO; + @JsonProperty("BYN") + private BigDecimal byn = BigDecimal.ZERO; + @JsonProperty("BYR") + private BigDecimal byr = BigDecimal.ZERO; + @JsonProperty("BZD") + private BigDecimal bzd = BigDecimal.ZERO; + @JsonProperty("CAD") + private BigDecimal cad = BigDecimal.ZERO; + @JsonProperty("CDF") + private BigDecimal cdf = BigDecimal.ZERO; + @JsonProperty("CHF") + private BigDecimal chf = BigDecimal.ZERO; + @JsonProperty("CLF") + private BigDecimal clf = BigDecimal.ZERO; + @JsonProperty("CLP") + private BigDecimal clp = BigDecimal.ZERO; + @JsonProperty("CNY") + private BigDecimal cny = BigDecimal.ZERO; + @JsonProperty("COP") + private BigDecimal cop = BigDecimal.ZERO; + @JsonProperty("CRC") + private BigDecimal crc = BigDecimal.ZERO; + @JsonProperty("CUC") + private BigDecimal cuc = BigDecimal.ZERO; + @JsonProperty("CVE") + private BigDecimal cve = BigDecimal.ZERO; + @JsonProperty("CZK") + private BigDecimal czk = BigDecimal.ZERO; + @JsonProperty("DJF") + private BigDecimal djf = BigDecimal.ZERO; + @JsonProperty("DKK") + private BigDecimal dkk = BigDecimal.ZERO; + @JsonProperty("DOP") + private BigDecimal dop = BigDecimal.ZERO; + @JsonProperty("DZD") + private BigDecimal dzd = BigDecimal.ZERO; + @JsonProperty("EEK") + private BigDecimal eek = BigDecimal.ZERO; + @JsonProperty("EGP") + private BigDecimal egp = BigDecimal.ZERO; + @JsonProperty("ERN") + private BigDecimal ern = BigDecimal.ZERO; + @JsonProperty("ETB") + private BigDecimal etb = BigDecimal.ZERO; + @JsonProperty("ETH") + private BigDecimal eth = BigDecimal.ZERO; + @JsonProperty("EUR") + private BigDecimal eur = BigDecimal.ZERO; + @JsonProperty("FJD") + private BigDecimal fjd = BigDecimal.ZERO; + @JsonProperty("FKP") + private BigDecimal fkp = BigDecimal.ZERO; + @JsonProperty("GBP") + private BigDecimal gbp = BigDecimal.ZERO; + @JsonProperty("GEL") + private BigDecimal gel = BigDecimal.ZERO; + @JsonProperty("GGP") + private BigDecimal ggp = BigDecimal.ZERO; + @JsonProperty("GHS") + private BigDecimal ghs = BigDecimal.ZERO; + @JsonProperty("GIP") + private BigDecimal gip = BigDecimal.ZERO; + @JsonProperty("GMD") + private BigDecimal gmd = BigDecimal.ZERO; + @JsonProperty("GNF") + private BigDecimal gnf = BigDecimal.ZERO; + @JsonProperty("GTQ") + private BigDecimal gtq = BigDecimal.ZERO; + @JsonProperty("GYD") + private BigDecimal gyd = BigDecimal.ZERO; + @JsonProperty("HKD") + private BigDecimal hkd = BigDecimal.ZERO; + @JsonProperty("HNL") + private BigDecimal hnl = BigDecimal.ZERO; + @JsonProperty("HRK") + private BigDecimal hrk = BigDecimal.ZERO; + @JsonProperty("HTG") + private BigDecimal htg = BigDecimal.ZERO; + @JsonProperty("HUF") + private BigDecimal huf = BigDecimal.ZERO; + @JsonProperty("IDR") + private BigDecimal idr = BigDecimal.ZERO; + @JsonProperty("ILS") + private BigDecimal ils = BigDecimal.ZERO; + @JsonProperty("IMP") + private BigDecimal imp = BigDecimal.ZERO; + @JsonProperty("INR") + private BigDecimal inr = BigDecimal.ZERO; + @JsonProperty("IQD") + private BigDecimal iqd = BigDecimal.ZERO; + @JsonProperty("ISK") + private BigDecimal isk = BigDecimal.ZERO; + @JsonProperty("JEP") + private BigDecimal jep = BigDecimal.ZERO; + @JsonProperty("JMD") + private BigDecimal jmd = BigDecimal.ZERO; + @JsonProperty("JOD") + private BigDecimal jod = BigDecimal.ZERO; + @JsonProperty("JPY") + private BigDecimal jpy = BigDecimal.ZERO; + @JsonProperty("KES") + private BigDecimal kes = BigDecimal.ZERO; + @JsonProperty("KGS") + private BigDecimal kgs = BigDecimal.ZERO; + @JsonProperty("KHR") + private BigDecimal khr = BigDecimal.ZERO; + @JsonProperty("KMF") + private BigDecimal kmf = BigDecimal.ZERO; + @JsonProperty("KRW") + private BigDecimal krw = BigDecimal.ZERO; + @JsonProperty("KWD") + private BigDecimal kwd = BigDecimal.ZERO; + @JsonProperty("KYD") + private BigDecimal kyd = BigDecimal.ZERO; + @JsonProperty("KZT") + private BigDecimal kzt = BigDecimal.ZERO; + @JsonProperty("LAK") + private BigDecimal lak = BigDecimal.ZERO; + @JsonProperty("LBP") + private BigDecimal lbp = BigDecimal.ZERO; + @JsonProperty("LKR") + private BigDecimal lkr = BigDecimal.ZERO; + @JsonProperty("LRD") + private BigDecimal lrd = BigDecimal.ZERO; + @JsonProperty("LSL") + private BigDecimal lsl = BigDecimal.ZERO; + @JsonProperty("LTC") + private BigDecimal ltc = BigDecimal.ZERO; + @JsonProperty("LTL") + private BigDecimal ltl = BigDecimal.ZERO; + @JsonProperty("LVL") + private BigDecimal lvl = BigDecimal.ZERO; + @JsonProperty("LYD") + private BigDecimal lyd = BigDecimal.ZERO; + @JsonProperty("MAD") + private BigDecimal mad = BigDecimal.ZERO; + @JsonProperty("MDL") + private BigDecimal mdl = BigDecimal.ZERO; + @JsonProperty("MGA") + private BigDecimal mga = BigDecimal.ZERO; + @JsonProperty("MKD") + private BigDecimal mkd = BigDecimal.ZERO; + @JsonProperty("MMK") + private BigDecimal mmk = BigDecimal.ZERO; + @JsonProperty("MNT") + private BigDecimal mnt = BigDecimal.ZERO; + @JsonProperty("MOP") + private BigDecimal mop = BigDecimal.ZERO; + @JsonProperty("MRO") + private BigDecimal mro = BigDecimal.ZERO; + @JsonProperty("MTL") + private BigDecimal mtl = BigDecimal.ZERO; + @JsonProperty("MUR") + private BigDecimal mur = BigDecimal.ZERO; + @JsonProperty("MVR") + private BigDecimal mvr = BigDecimal.ZERO; + @JsonProperty("MWK") + private BigDecimal mwk = BigDecimal.ZERO; + @JsonProperty("MXN") + private BigDecimal mxn = BigDecimal.ZERO; + @JsonProperty("MYR") + private BigDecimal myr = BigDecimal.ZERO; + @JsonProperty("MZN") + private BigDecimal mzn = BigDecimal.ZERO; + @JsonProperty("NAD") + private BigDecimal nad = BigDecimal.ZERO; + @JsonProperty("NGN") + private BigDecimal ngn = BigDecimal.ZERO; + @JsonProperty("NIO") + private BigDecimal nio = BigDecimal.ZERO; + @JsonProperty("NOK") + private BigDecimal nok = BigDecimal.ZERO; + @JsonProperty("NPR") + private BigDecimal npr = BigDecimal.ZERO; + @JsonProperty("NZD") + private BigDecimal nzd = BigDecimal.ZERO; + @JsonProperty("OMR") + private BigDecimal omr = BigDecimal.ZERO; + @JsonProperty("PAB") + private BigDecimal pab = BigDecimal.ZERO; + @JsonProperty("PEN") + private BigDecimal pen = BigDecimal.ZERO; + @JsonProperty("PGK") + private BigDecimal pgk = BigDecimal.ZERO; + @JsonProperty("PHP") + private BigDecimal php = BigDecimal.ZERO; + @JsonProperty("PKR") + private BigDecimal pkr = BigDecimal.ZERO; + @JsonProperty("PLN") + private BigDecimal pln = BigDecimal.ZERO; + @JsonProperty("PYG") + private BigDecimal pyg = BigDecimal.ZERO; + @JsonProperty("QAR") + private BigDecimal qar = BigDecimal.ZERO; + @JsonProperty("RON") + private BigDecimal ron = BigDecimal.ZERO; + @JsonProperty("RSD") + private BigDecimal rsd = BigDecimal.ZERO; + @JsonProperty("RUB") + private BigDecimal rub = BigDecimal.ZERO; + @JsonProperty("RWF") + private BigDecimal rwf = BigDecimal.ZERO; + @JsonProperty("SAR") + private BigDecimal sar = BigDecimal.ZERO; + @JsonProperty("SBD") + private BigDecimal sbd = BigDecimal.ZERO; + @JsonProperty("SCR") + private BigDecimal scr = BigDecimal.ZERO; + @JsonProperty("SEK") + private BigDecimal sek = BigDecimal.ZERO; + @JsonProperty("SGD") + private BigDecimal sgd = BigDecimal.ZERO; + @JsonProperty("SHP") + private BigDecimal shp = BigDecimal.ZERO; + @JsonProperty("SLL") + private BigDecimal sll = BigDecimal.ZERO; + @JsonProperty("SOS") + private BigDecimal sos = BigDecimal.ZERO; + @JsonProperty("SRD") + private BigDecimal srd = BigDecimal.ZERO; + @JsonProperty("SSP") + private BigDecimal ssp = BigDecimal.ZERO; + @JsonProperty("STD") + private BigDecimal std = BigDecimal.ZERO; + @JsonProperty("SVC") + private BigDecimal svc = BigDecimal.ZERO; + @JsonProperty("SZL") + private BigDecimal szl = BigDecimal.ZERO; + @JsonProperty("THB") + private BigDecimal thb = BigDecimal.ZERO; + @JsonProperty("TJS") + private BigDecimal tjs = BigDecimal.ZERO; + @JsonProperty("TMT") + private BigDecimal tmt = BigDecimal.ZERO; + @JsonProperty("TND") + private BigDecimal tnd = BigDecimal.ZERO; + @JsonProperty("TOP") + private BigDecimal top = BigDecimal.ZERO; + private BigDecimal try1 = BigDecimal.ZERO; + @JsonProperty("TTD") + private BigDecimal ttd = BigDecimal.ZERO; + @JsonProperty("TWD") + private BigDecimal twd = BigDecimal.ZERO; + @JsonProperty("TZS") + private BigDecimal tzs = BigDecimal.ZERO; + @JsonProperty("UAH") + private BigDecimal uah = BigDecimal.ZERO; + @JsonProperty("UGX") + private BigDecimal ugx = BigDecimal.ZERO; + @JsonProperty("USD") + private BigDecimal usd = BigDecimal.ZERO; + @JsonProperty("UYU") + private BigDecimal uyu = BigDecimal.ZERO; + @JsonProperty("UZS") + private BigDecimal uzs = BigDecimal.ZERO; + @JsonProperty("VEF") + private BigDecimal vef = BigDecimal.ZERO; + @JsonProperty("VND") + private BigDecimal vnd = BigDecimal.ZERO; + @JsonProperty("VUV") + private BigDecimal vuv = BigDecimal.ZERO; + @JsonProperty("WST") + private BigDecimal wst = BigDecimal.ZERO; + @JsonProperty("XAF") + private BigDecimal xaf = BigDecimal.ZERO; + @JsonProperty("XAG") + private BigDecimal xag = BigDecimal.ZERO; + @JsonProperty("XAU") + private BigDecimal xau = BigDecimal.ZERO; + @JsonProperty("XCD") + private BigDecimal xcd = BigDecimal.ZERO; + @JsonProperty("XDR") + private BigDecimal xdr = BigDecimal.ZERO; + @JsonProperty("XOF") + private BigDecimal xof = BigDecimal.ZERO; + @JsonProperty("XPD") + private BigDecimal xpd = BigDecimal.ZERO; + @JsonProperty("XPF") + private BigDecimal xpf = BigDecimal.ZERO; + @JsonProperty("XPT") + private BigDecimal xpt = BigDecimal.ZERO; + @JsonProperty("YER") + private BigDecimal yer = BigDecimal.ZERO; + @JsonProperty("ZAR") + private BigDecimal zar = BigDecimal.ZERO; + @JsonProperty("ZMK") + private BigDecimal zmk = BigDecimal.ZERO; + @JsonProperty("ZMW") + private BigDecimal zmw = BigDecimal.ZERO; + @JsonProperty("ZWL") + private BigDecimal zwl = BigDecimal.ZERO; + @JsonProperty("VES") + private BigDecimal ves = BigDecimal.ZERO; + @JsonProperty("XBA") + private BigDecimal xba = BigDecimal.ZERO; + @JsonProperty("XTS") + private BigDecimal xts = BigDecimal.ZERO; + @JsonProperty("GBX") + private BigDecimal gbx = BigDecimal.ZERO; + @JsonProperty("CNH") + private BigDecimal cnh = BigDecimal.ZERO; + @JsonProperty("ZWD") + private BigDecimal zwd = BigDecimal.ZERO; + @JsonProperty("BCH") + private BigDecimal bch = BigDecimal.ZERO; + @JsonProperty("BSV") + private BigDecimal bsv = BigDecimal.ZERO; + @JsonProperty("ETH2") + private BigDecimal eth2 = BigDecimal.ZERO; + @JsonProperty("ETC") + private BigDecimal etc = BigDecimal.ZERO; + @JsonProperty("ZRX") + private BigDecimal zrx = BigDecimal.ZERO; + @JsonProperty("USDC") + private BigDecimal usdc = BigDecimal.ZERO; + @JsonProperty("BAT") + private BigDecimal bat = BigDecimal.ZERO; + @JsonProperty("LOOM") + private BigDecimal loom = BigDecimal.ZERO; + @JsonProperty("MANA") + private BigDecimal mana = BigDecimal.ZERO; + @JsonProperty("KNC") + private BigDecimal knc = BigDecimal.ZERO; + @JsonProperty("LINK") + private BigDecimal link = BigDecimal.ZERO; + @JsonProperty("MKR") + private BigDecimal mkr = BigDecimal.ZERO; + @JsonProperty("CVC") + private BigDecimal cvc = BigDecimal.ZERO; + @JsonProperty("OMG") + private BigDecimal omg = BigDecimal.ZERO; + @JsonProperty("GNT") + private BigDecimal gnt = BigDecimal.ZERO; + @JsonProperty("DAI") + private BigDecimal dai = BigDecimal.ZERO; + @JsonProperty("SNT") + private BigDecimal snt = BigDecimal.ZERO; + @JsonProperty("ZEC") + private BigDecimal zec = BigDecimal.ZERO; + @JsonProperty("XRP") + private BigDecimal xrp = BigDecimal.ZERO; + @JsonProperty("REP") + private BigDecimal rep = BigDecimal.ZERO; + @JsonProperty("XLM") + private BigDecimal xlm = BigDecimal.ZERO; + @JsonProperty("EOS") + private BigDecimal eos = BigDecimal.ZERO; + @JsonProperty("XTZ") + private BigDecimal xtz = BigDecimal.ZERO; + @JsonProperty("ALGO") + private BigDecimal algo = BigDecimal.ZERO; + @JsonProperty("DASH") + private BigDecimal dash = BigDecimal.ZERO; + @JsonProperty("ATOM") + private BigDecimal atom = BigDecimal.ZERO; + @JsonProperty("OXT") + private BigDecimal oxt = BigDecimal.ZERO; + @JsonProperty("COMP") + private BigDecimal comp = BigDecimal.ZERO; + @JsonProperty("ENJ") + private BigDecimal enj = BigDecimal.ZERO; + @JsonProperty("REPV2") + private BigDecimal repv2 = BigDecimal.ZERO; + @JsonProperty("BAND") + private BigDecimal band = BigDecimal.ZERO; + @JsonProperty("NMR") + private BigDecimal nmr = BigDecimal.ZERO; + @JsonProperty("CGLD") + private BigDecimal cgld = BigDecimal.ZERO; + @JsonProperty("UMA") + private BigDecimal uma = BigDecimal.ZERO; + @JsonProperty("LRC") + private BigDecimal lrc = BigDecimal.ZERO; + @JsonProperty("YFI") + private BigDecimal yfi = BigDecimal.ZERO; + @JsonProperty("UNI") + private BigDecimal uni = BigDecimal.ZERO; + @JsonProperty("BAL") + private BigDecimal bal = BigDecimal.ZERO; + @JsonProperty("REN") + private BigDecimal ren = BigDecimal.ZERO; + @JsonProperty("WBTC") + private BigDecimal wbtc = BigDecimal.ZERO; + @JsonProperty("NU") + private BigDecimal nu = BigDecimal.ZERO; + @JsonProperty("YFII") + private BigDecimal yfii = BigDecimal.ZERO; + @JsonProperty("FIL") + private BigDecimal fil = BigDecimal.ZERO; + @JsonProperty("AAVE") + private BigDecimal aave = BigDecimal.ZERO; + @JsonProperty("BNT") + private BigDecimal bnt = BigDecimal.ZERO; + @JsonProperty("GRT") + private BigDecimal grt = BigDecimal.ZERO; + @JsonProperty("SNX") + private BigDecimal snx = BigDecimal.ZERO; + @JsonProperty("STORJ") + private BigDecimal storj = BigDecimal.ZERO; + @JsonProperty("SUSHI") + private BigDecimal sushi = BigDecimal.ZERO; + @JsonProperty("MATIC") + private BigDecimal matic = BigDecimal.ZERO; + @JsonProperty("SKL") + private BigDecimal skl = BigDecimal.ZERO; + @JsonProperty("ADA") + private BigDecimal ada = BigDecimal.ZERO; + @JsonProperty("ANKR") + private BigDecimal ankr = BigDecimal.ZERO; + @JsonProperty("CRV") + private BigDecimal crv = BigDecimal.ZERO; + @JsonProperty("ICP") + private BigDecimal icp = BigDecimal.ZERO; + @JsonProperty("NKN") + private BigDecimal nkn = BigDecimal.ZERO; + @JsonProperty("OGN") + private BigDecimal ogn = BigDecimal.ZERO; + private BigDecimal inch1 = BigDecimal.ZERO; + @JsonProperty("USDT") + private BigDecimal usdt = BigDecimal.ZERO; + @JsonProperty("FORTH") + private BigDecimal forth = BigDecimal.ZERO; + @JsonProperty("CTSI") + private BigDecimal ctsi = BigDecimal.ZERO; + @JsonProperty("TRB") + private BigDecimal trb = BigDecimal.ZERO; + @JsonProperty("POLY") + private BigDecimal poly = BigDecimal.ZERO; + @JsonProperty("MIR") + private BigDecimal mir = BigDecimal.ZERO; + @JsonProperty("RLC") + private BigDecimal rlc = BigDecimal.ZERO; + @JsonProperty("DOT") + private BigDecimal dot = BigDecimal.ZERO; + @JsonProperty("SOL") + private BigDecimal sol = BigDecimal.ZERO; + @JsonProperty("DOGE") + private BigDecimal doge = BigDecimal.ZERO; + @JsonProperty("MLN") + private BigDecimal mln = BigDecimal.ZERO; + @JsonProperty("GTC") + private BigDecimal gtc = BigDecimal.ZERO; + @JsonProperty("AMP") + private BigDecimal amp = BigDecimal.ZERO; + @JsonProperty("SHIB") + private BigDecimal shib = BigDecimal.ZERO; + @JsonProperty("CHZ") + private BigDecimal chz = BigDecimal.ZERO; + @JsonProperty("KEEP") + private BigDecimal keep = BigDecimal.ZERO; + @JsonProperty("LPT") + private BigDecimal lpt = BigDecimal.ZERO; + @JsonProperty("QNT") + private BigDecimal qnt = BigDecimal.ZERO; + @JsonProperty("BOND") + private BigDecimal bond = BigDecimal.ZERO; + @JsonProperty("RLY") + private BigDecimal rly = BigDecimal.ZERO; + @JsonProperty("CLV") + private BigDecimal clv = BigDecimal.ZERO; + @JsonProperty("FARM") + private BigDecimal farm = BigDecimal.ZERO; + @JsonProperty("MASK") + private BigDecimal mask = BigDecimal.ZERO; + @JsonProperty("FET") + private BigDecimal fet = BigDecimal.ZERO; + @JsonProperty("PAX") + private BigDecimal pax = BigDecimal.ZERO; + @JsonProperty("ACH") + private BigDecimal ach = BigDecimal.ZERO; + @JsonProperty("ASM") + private BigDecimal asm = BigDecimal.ZERO; + @JsonProperty("PLA") + private BigDecimal pla = BigDecimal.ZERO; + @JsonProperty("RAI") + private BigDecimal rai = BigDecimal.ZERO; + @JsonProperty("TRIBE") + private BigDecimal tribe = BigDecimal.ZERO; + @JsonProperty("ORN") + private BigDecimal orn = BigDecimal.ZERO; + @JsonProperty("IOTX") + private BigDecimal iotx = BigDecimal.ZERO; + @JsonProperty("UST") + private BigDecimal ust = BigDecimal.ZERO; + @JsonProperty("QUICK") + private BigDecimal quick = BigDecimal.ZERO; + @JsonProperty("AXS") + private BigDecimal axs = BigDecimal.ZERO; + @JsonProperty("REQ") + private BigDecimal req = BigDecimal.ZERO; + @JsonProperty("WLUNA") + private BigDecimal wluna = BigDecimal.ZERO; + @JsonProperty("TRU") + private BigDecimal tru = BigDecimal.ZERO; + @JsonProperty("RAD") + private BigDecimal rad = BigDecimal.ZERO; + @JsonProperty("COTI") + private BigDecimal coti = BigDecimal.ZERO; + @JsonProperty("DDX") + private BigDecimal ddx = BigDecimal.ZERO; + @JsonProperty("SUKU") + private BigDecimal suku = BigDecimal.ZERO; + @JsonProperty("RGT") + private BigDecimal rgt = BigDecimal.ZERO; + @JsonProperty("XYO") + private BigDecimal xyo = BigDecimal.ZERO; + @JsonProperty("ZEN") + private BigDecimal zen = BigDecimal.ZERO; + @JsonProperty("AUCTION") + private BigDecimal auction = BigDecimal.ZERO; + @JsonProperty("BUSD") + private BigDecimal busd = BigDecimal.ZERO; + @JsonProperty("JASMY") + private BigDecimal jasmy = BigDecimal.ZERO; + @JsonProperty("WCFG") + private BigDecimal wcfg = BigDecimal.ZERO; + @JsonProperty("BTRST") + private BigDecimal btrst = BigDecimal.ZERO; + @JsonProperty("AGLD") + private BigDecimal agld = BigDecimal.ZERO; + @JsonProperty("AVAX") + private BigDecimal avax = BigDecimal.ZERO; + @JsonProperty("FX") + private BigDecimal fx = BigDecimal.ZERO; + @JsonProperty("TRAC") + private BigDecimal trac = BigDecimal.ZERO; + @JsonProperty("LCX") + private BigDecimal lcx = BigDecimal.ZERO; + @JsonProperty("ARPA") + private BigDecimal arpa = BigDecimal.ZERO; + @JsonProperty("BADGER") + private BigDecimal badger = BigDecimal.ZERO; + @JsonProperty("KRL") + private BigDecimal krl = BigDecimal.ZERO; + @JsonProperty("PERP") + private BigDecimal perp = BigDecimal.ZERO; + @JsonProperty("RARI") + private BigDecimal rari = BigDecimal.ZERO; + @JsonProperty("DESO") + private BigDecimal deso = BigDecimal.ZERO; + @JsonProperty("API3") + private BigDecimal api3 = BigDecimal.ZERO; + @JsonProperty("NCT") + private BigDecimal nct = BigDecimal.ZERO; + @JsonProperty("SHPING") + private BigDecimal shping = BigDecimal.ZERO; + @JsonProperty("UPI") + private BigDecimal upi = BigDecimal.ZERO; + @JsonProperty("CRO") + private BigDecimal cro = BigDecimal.ZERO; + @JsonProperty("AVT") + private BigDecimal avt = BigDecimal.ZERO; + @JsonProperty("MDT") + private BigDecimal mdt = BigDecimal.ZERO; + @JsonProperty("VGX") + private BigDecimal vgx = BigDecimal.ZERO; + @JsonProperty("ALCX") + private BigDecimal alcx = BigDecimal.ZERO; + @JsonProperty("COVAL") + private BigDecimal coval = BigDecimal.ZERO; + @JsonProperty("FOX") + private BigDecimal fox = BigDecimal.ZERO; + @JsonProperty("MUSD") + private BigDecimal musd = BigDecimal.ZERO; + @JsonProperty("GALA") + private BigDecimal gala = BigDecimal.ZERO; + @JsonProperty("POWR") + private BigDecimal powr = BigDecimal.ZERO; + @JsonProperty("GYEN") + private BigDecimal gyen = BigDecimal.ZERO; + @JsonProperty("ALICE") + private BigDecimal alice = BigDecimal.ZERO; + @JsonProperty("INV") + private BigDecimal inv = BigDecimal.ZERO; + @JsonProperty("LQTY") + private BigDecimal lqty = BigDecimal.ZERO; + @JsonProperty("PRO") + private BigDecimal pro = BigDecimal.ZERO; + @JsonProperty("SPELL") + private BigDecimal spell = BigDecimal.ZERO; + @JsonProperty("ENS") + private BigDecimal ens = BigDecimal.ZERO; + @JsonProperty("DIA") + private BigDecimal dia = BigDecimal.ZERO; + @JsonProperty("BLZ") + private BigDecimal blz = BigDecimal.ZERO; + @JsonProperty("CTX") + private BigDecimal ctx = BigDecimal.ZERO; + @JsonProperty("IDEX") + private BigDecimal idex = BigDecimal.ZERO; + @JsonProperty("MCO2") + private BigDecimal mco2 = BigDecimal.ZERO; + @JsonProperty("POLS") + private BigDecimal pols = BigDecimal.ZERO; + private BigDecimal super1 = BigDecimal.ZERO; + @JsonProperty("UNFI") + private BigDecimal unfi = BigDecimal.ZERO; + @JsonProperty("STX") + private BigDecimal stx = BigDecimal.ZERO; + @JsonProperty("GODS") + private BigDecimal gods = BigDecimal.ZERO; + @JsonProperty("IMX") + private BigDecimal imx = BigDecimal.ZERO; + @JsonProperty("RBN") + private BigDecimal rbn = BigDecimal.ZERO; + @JsonProperty("BICO") + private BigDecimal bico = BigDecimal.ZERO; + @JsonProperty("GFI") + private BigDecimal gfi = BigDecimal.ZERO; + @JsonProperty("GLM") + private BigDecimal glm = BigDecimal.ZERO; + @JsonProperty("MPL") + private BigDecimal mpl = BigDecimal.ZERO; + @JsonProperty("PLU") + private BigDecimal plu = BigDecimal.ZERO; + @JsonProperty("FIDA") + private BigDecimal fida = BigDecimal.ZERO; + @JsonProperty("ORCA") + private BigDecimal orca = BigDecimal.ZERO; + @JsonProperty("CRPT") + private BigDecimal crpt = BigDecimal.ZERO; + @JsonProperty("QSP") + private BigDecimal qsp = BigDecimal.ZERO; + @JsonProperty("RNDR") + private BigDecimal rndr = BigDecimal.ZERO; + @JsonProperty("SYN") + private BigDecimal syn = BigDecimal.ZERO; + @JsonProperty("AIOZ") + private BigDecimal aioz = BigDecimal.ZERO; + @JsonProperty("AERGO") + private BigDecimal aergo = BigDecimal.ZERO; + @JsonProperty("HIGH") + private BigDecimal high = BigDecimal.ZERO; + @JsonProperty("ROSE") + private BigDecimal rose = BigDecimal.ZERO; + @JsonProperty("APE") + private BigDecimal ape = BigDecimal.ZERO; + @JsonProperty("MINA") + private BigDecimal mina = BigDecimal.ZERO; + @JsonProperty("GMT") + private BigDecimal gmt = BigDecimal.ZERO; + @JsonProperty("GST") + private BigDecimal gst = BigDecimal.ZERO; + @JsonProperty("GAL") + private BigDecimal gal = BigDecimal.ZERO; + @JsonProperty("DNT") + private BigDecimal dnt = BigDecimal.ZERO; + @JsonProperty("FLOW") + private BigDecimal flow = BigDecimal.ZERO; + @JsonProperty("SAND") + private BigDecimal sand = BigDecimal.ZERO; + @JsonProperty("OP") + private BigDecimal op = BigDecimal.ZERO; + @JsonProperty("KSM") + private BigDecimal ksm = BigDecimal.ZERO; + + @JsonProperty("SUPER") + public void setSuper(BigDecimal super1) { + this.super1 = super1; + } + + @JsonProperty("1INCH") + public void set1Inch(BigDecimal inch1) { + this.inch1 = inch1; + } + + public void setTry1(BigDecimal try1) { + this.try1 = try1; + } + + public void setAed(BigDecimal aed) { + this.aed = aed; + } + + public void setAfn(BigDecimal afn) { + this.afn = afn; + } + + public void setAll(BigDecimal all) { + this.all = all; + } + + public void setAmd(BigDecimal amd) { + this.amd = amd; + } + + public void setAng(BigDecimal ang) { + this.ang = ang; + } + + public void setAoa(BigDecimal aoa) { + this.aoa = aoa; + } + + public void setArs(BigDecimal ars) { + this.ars = ars; + } + + public void setAud(BigDecimal aud) { + this.aud = aud; + } + + public BigDecimal getOp() { + return op; + } + + public void setOp(BigDecimal op) { + this.op = op; + } + + public void setAwg(BigDecimal awg) { + this.awg = awg; + } + + public void setAzn(BigDecimal azn) { + this.azn = azn; + } + + public void setBam(BigDecimal bam) { + this.bam = bam; + } + + public void setBbd(BigDecimal bbd) { + this.bbd = bbd; + } + + public void setBdt(BigDecimal bdt) { + this.bdt = bdt; + } + + public void setBgn(BigDecimal bgn) { + this.bgn = bgn; + } + + public void setBhd(BigDecimal bhd) { + this.bhd = bhd; + } + + public void setBif(BigDecimal bif) { + this.bif = bif; + } + + public void setBmd(BigDecimal bmd) { + this.bmd = bmd; + } + + public void setBnd(BigDecimal bnd) { + this.bnd = bnd; + } + + public void setBob(BigDecimal bob) { + this.bob = bob; + } + + public void setBrl(BigDecimal brl) { + this.brl = brl; + } + + public void setBsd(BigDecimal bsd) { + this.bsd = bsd; + } + + public void setBtc(BigDecimal btc) { + this.btc = btc; + } + + public void setBtn(BigDecimal btn) { + this.btn = btn; + } + + public void setBwp(BigDecimal bwp) { + this.bwp = bwp; + } + + public void setByn(BigDecimal byn) { + this.byn = byn; + } + + public void setByr(BigDecimal byr) { + this.byr = byr; + } + + public void setBzd(BigDecimal bzd) { + this.bzd = bzd; + } + + public void setCad(BigDecimal cad) { + this.cad = cad; + } + + public void setCdf(BigDecimal cdf) { + this.cdf = cdf; + } + + public void setChf(BigDecimal chf) { + this.chf = chf; + } + + public void setClf(BigDecimal clf) { + this.clf = clf; + } + + public void setClp(BigDecimal clp) { + this.clp = clp; + } + + public void setCny(BigDecimal cny) { + this.cny = cny; + } + + public void setCop(BigDecimal cop) { + this.cop = cop; + } + + public void setCrc(BigDecimal crc) { + this.crc = crc; + } + + public void setCuc(BigDecimal cuc) { + this.cuc = cuc; + } + + public void setCve(BigDecimal cve) { + this.cve = cve; + } + + public void setCzk(BigDecimal czk) { + this.czk = czk; + } + + public void setDjf(BigDecimal djf) { + this.djf = djf; + } + + public void setDkk(BigDecimal dkk) { + this.dkk = dkk; + } + + public void setDop(BigDecimal dop) { + this.dop = dop; + } + + public void setDzd(BigDecimal dzd) { + this.dzd = dzd; + } + + public void setEek(BigDecimal eek) { + this.eek = eek; + } + + public void setEgp(BigDecimal egp) { + this.egp = egp; + } + + public void setErn(BigDecimal ern) { + this.ern = ern; + } + + public void setEtb(BigDecimal etb) { + this.etb = etb; + } + + public void setEth(BigDecimal eth) { + this.eth = eth; + } + + public void setEur(BigDecimal eur) { + this.eur = eur; + } + + public void setFjd(BigDecimal fjd) { + this.fjd = fjd; + } + + public void setFkp(BigDecimal fkp) { + this.fkp = fkp; + } + + public void setGbp(BigDecimal gbp) { + this.gbp = gbp; + } + + public void setGel(BigDecimal gel) { + this.gel = gel; + } + + public void setGgp(BigDecimal ggp) { + this.ggp = ggp; + } + + public void setGhs(BigDecimal ghs) { + this.ghs = ghs; + } + + public void setGip(BigDecimal gip) { + this.gip = gip; + } + + public void setGmd(BigDecimal gmd) { + this.gmd = gmd; + } + + public void setGnf(BigDecimal gnf) { + this.gnf = gnf; + } + + public void setGtq(BigDecimal gtq) { + this.gtq = gtq; + } + + public void setGyd(BigDecimal gyd) { + this.gyd = gyd; + } + + public void setHkd(BigDecimal hkd) { + this.hkd = hkd; + } + + public void setHnl(BigDecimal hnl) { + this.hnl = hnl; + } + + public void setHrk(BigDecimal hrk) { + this.hrk = hrk; + } + + public void setHtg(BigDecimal htg) { + this.htg = htg; + } + + public void setHuf(BigDecimal huf) { + this.huf = huf; + } + + public void setIdr(BigDecimal idr) { + this.idr = idr; + } + + public void setIls(BigDecimal ils) { + this.ils = ils; + } + + public void setImp(BigDecimal imp) { + this.imp = imp; + } + + public void setInr(BigDecimal inr) { + this.inr = inr; + } + + public void setIqd(BigDecimal iqd) { + this.iqd = iqd; + } + + public void setIsk(BigDecimal isk) { + this.isk = isk; + } + + public void setJep(BigDecimal jep) { + this.jep = jep; + } + + public void setJmd(BigDecimal jmd) { + this.jmd = jmd; + } + + public void setJod(BigDecimal jod) { + this.jod = jod; + } + + public void setJpy(BigDecimal jpy) { + this.jpy = jpy; + } + + public void setKes(BigDecimal kes) { + this.kes = kes; + } + + public void setKgs(BigDecimal kgs) { + this.kgs = kgs; + } + + public void setKhr(BigDecimal khr) { + this.khr = khr; + } + + public void setKmf(BigDecimal kmf) { + this.kmf = kmf; + } + + public void setKrw(BigDecimal krw) { + this.krw = krw; + } + + public void setKwd(BigDecimal kwd) { + this.kwd = kwd; + } + + public void setKyd(BigDecimal kyd) { + this.kyd = kyd; + } + + public void setKzt(BigDecimal kzt) { + this.kzt = kzt; + } + + public void setLak(BigDecimal lak) { + this.lak = lak; + } + + public void setLbp(BigDecimal lbp) { + this.lbp = lbp; + } + + public void setLkr(BigDecimal lkr) { + this.lkr = lkr; + } + + public void setLrd(BigDecimal lrd) { + this.lrd = lrd; + } + + public void setLsl(BigDecimal lsl) { + this.lsl = lsl; + } + + public void setLtc(BigDecimal ltc) { + this.ltc = ltc; + } + + public void setLtl(BigDecimal ltl) { + this.ltl = ltl; + } + + public void setLvl(BigDecimal lvl) { + this.lvl = lvl; + } + + public void setLyd(BigDecimal lyd) { + this.lyd = lyd; + } + + public void setMad(BigDecimal mad) { + this.mad = mad; + } + + public void setMdl(BigDecimal mdl) { + this.mdl = mdl; + } + + public void setMga(BigDecimal mga) { + this.mga = mga; + } + + public void setMkd(BigDecimal mkd) { + this.mkd = mkd; + } + + public void setMmk(BigDecimal mmk) { + this.mmk = mmk; + } + + public void setMnt(BigDecimal mnt) { + this.mnt = mnt; + } + + public void setMop(BigDecimal mop) { + this.mop = mop; + } + + public void setMro(BigDecimal mro) { + this.mro = mro; + } + + public void setMtl(BigDecimal mtl) { + this.mtl = mtl; + } + + public void setMur(BigDecimal mur) { + this.mur = mur; + } + + public void setMvr(BigDecimal mvr) { + this.mvr = mvr; + } + + public void setMwk(BigDecimal mwk) { + this.mwk = mwk; + } + + public void setMxn(BigDecimal mxn) { + this.mxn = mxn; + } + + public void setMyr(BigDecimal myr) { + this.myr = myr; + } + + public void setMzn(BigDecimal mzn) { + this.mzn = mzn; + } + + public void setNad(BigDecimal nad) { + this.nad = nad; + } + + public void setNgn(BigDecimal ngn) { + this.ngn = ngn; + } + + public void setNio(BigDecimal nio) { + this.nio = nio; + } + + public void setNok(BigDecimal nok) { + this.nok = nok; + } + + public void setNpr(BigDecimal npr) { + this.npr = npr; + } + + public void setNzd(BigDecimal nzd) { + this.nzd = nzd; + } + + public void setOmr(BigDecimal omr) { + this.omr = omr; + } + + public void setPab(BigDecimal pab) { + this.pab = pab; + } + + public void setPen(BigDecimal pen) { + this.pen = pen; + } + + public void setPgk(BigDecimal pgk) { + this.pgk = pgk; + } + + public void setPhp(BigDecimal php) { + this.php = php; + } + + public void setPkr(BigDecimal pkr) { + this.pkr = pkr; + } + + public void setPln(BigDecimal pln) { + this.pln = pln; + } + + public void setPyg(BigDecimal pyg) { + this.pyg = pyg; + } + + public void setQar(BigDecimal qar) { + this.qar = qar; + } + + public void setRon(BigDecimal ron) { + this.ron = ron; + } + + public void setRsd(BigDecimal rsd) { + this.rsd = rsd; + } + + public void setRub(BigDecimal rub) { + this.rub = rub; + } + + public void setRwf(BigDecimal rwf) { + this.rwf = rwf; + } + + public void setSar(BigDecimal sar) { + this.sar = sar; + } + + public void setSbd(BigDecimal sbd) { + this.sbd = sbd; + } + + public void setScr(BigDecimal scr) { + this.scr = scr; + } + + public void setSek(BigDecimal sek) { + this.sek = sek; + } + + public void setSgd(BigDecimal sgd) { + this.sgd = sgd; + } + + public void setShp(BigDecimal shp) { + this.shp = shp; + } + + public void setSll(BigDecimal sll) { + this.sll = sll; + } + + public void setSos(BigDecimal sos) { + this.sos = sos; + } + + public void setSrd(BigDecimal srd) { + this.srd = srd; + } + + public void setSsp(BigDecimal ssp) { + this.ssp = ssp; + } + + public void setStd(BigDecimal std) { + this.std = std; + } + + public void setSvc(BigDecimal svc) { + this.svc = svc; + } + + public void setSzl(BigDecimal szl) { + this.szl = szl; + } + + public void setThb(BigDecimal thb) { + this.thb = thb; + } + + public void setTjs(BigDecimal tjs) { + this.tjs = tjs; + } + + public void setTmt(BigDecimal tmt) { + this.tmt = tmt; + } + + public void setTnd(BigDecimal tnd) { + this.tnd = tnd; + } + + public void setTop(BigDecimal top) { + this.top = top; + } + + @JsonProperty("TRY") + public void setTry(BigDecimal try1) { + this.try1 = try1; + } + + public void setTtd(BigDecimal ttd) { + this.ttd = ttd; + } + + public void setTwd(BigDecimal twd) { + this.twd = twd; + } + + public void setTzs(BigDecimal tzs) { + this.tzs = tzs; + } + + public void setUah(BigDecimal uah) { + this.uah = uah; + } + + public void setUgx(BigDecimal ugx) { + this.ugx = ugx; + } + + public void setUsd(BigDecimal usd) { + this.usd = usd; + } + + public void setUyu(BigDecimal uyu) { + this.uyu = uyu; + } + + public void setUzs(BigDecimal uzs) { + this.uzs = uzs; + } + + public void setVef(BigDecimal vef) { + this.vef = vef; + } + + public void setVnd(BigDecimal vnd) { + this.vnd = vnd; + } + + public void setVuv(BigDecimal vuv) { + this.vuv = vuv; + } + + public void setWst(BigDecimal wst) { + this.wst = wst; + } + + public void setXaf(BigDecimal xaf) { + this.xaf = xaf; + } + + public void setXag(BigDecimal xag) { + this.xag = xag; + } + + public void setXau(BigDecimal xau) { + this.xau = xau; + } + + public void setXcd(BigDecimal xcd) { + this.xcd = xcd; + } + + public void setXdr(BigDecimal xdr) { + this.xdr = xdr; + } + + public void setXof(BigDecimal xof) { + this.xof = xof; + } + + public void setXpd(BigDecimal xpd) { + this.xpd = xpd; + } + + public void setXpf(BigDecimal xpf) { + this.xpf = xpf; + } + + public void setXpt(BigDecimal xpt) { + this.xpt = xpt; + } + + public void setYer(BigDecimal yer) { + this.yer = yer; + } + + public void setZar(BigDecimal zar) { + this.zar = zar; + } + + public void setZmk(BigDecimal zmk) { + this.zmk = zmk; + } + + public void setZmw(BigDecimal zmw) { + this.zmw = zmw; + } + + public void setZwl(BigDecimal zwl) { + this.zwl = zwl; + } + + public void setVes(BigDecimal ves) { + this.ves = ves; + } + + public void setXba(BigDecimal xba) { + this.xba = xba; + } + + public void setXts(BigDecimal xts) { + this.xts = xts; + } + + public void setGbx(BigDecimal gbx) { + this.gbx = gbx; + } + + public void setCnh(BigDecimal cnh) { + this.cnh = cnh; + } + + public void setZwd(BigDecimal zwd) { + this.zwd = zwd; + } + + public void setBch(BigDecimal bch) { + this.bch = bch; + } + + public void setBsv(BigDecimal bsv) { + this.bsv = bsv; + } + + public void setEth2(BigDecimal eth2) { + this.eth2 = eth2; + } + + public void setEtc(BigDecimal etc) { + this.etc = etc; + } + + public void setZrx(BigDecimal zrx) { + this.zrx = zrx; + } + + public void setUsdc(BigDecimal usdc) { + this.usdc = usdc; + } + + public void setBat(BigDecimal bat) { + this.bat = bat; + } + + public void setLoom(BigDecimal loom) { + this.loom = loom; + } + + public void setMana(BigDecimal mana) { + this.mana = mana; + } + + public void setKnc(BigDecimal knc) { + this.knc = knc; + } + + public void setLink(BigDecimal link) { + this.link = link; + } + + public void setMkr(BigDecimal mkr) { + this.mkr = mkr; + } + + public void setCvc(BigDecimal cvc) { + this.cvc = cvc; + } + + public void setOmg(BigDecimal omg) { + this.omg = omg; + } + + public void setGnt(BigDecimal gnt) { + this.gnt = gnt; + } + + public void setDai(BigDecimal dai) { + this.dai = dai; + } + + public void setSnt(BigDecimal snt) { + this.snt = snt; + } + + public void setZec(BigDecimal zec) { + this.zec = zec; + } + + public void setXrp(BigDecimal xrp) { + this.xrp = xrp; + } + + public void setRep(BigDecimal rep) { + this.rep = rep; + } + + public void setXlm(BigDecimal xlm) { + this.xlm = xlm; + } + + public void setEos(BigDecimal eos) { + this.eos = eos; + } + + public void setXtz(BigDecimal xtz) { + this.xtz = xtz; + } + + public void setAlgo(BigDecimal algo) { + this.algo = algo; + } + + public void setDash(BigDecimal dash) { + this.dash = dash; + } + + public void setAtom(BigDecimal atom) { + this.atom = atom; + } + + public void setOxt(BigDecimal oxt) { + this.oxt = oxt; + } + + public void setComp(BigDecimal comp) { + this.comp = comp; + } + + public void setEnj(BigDecimal enj) { + this.enj = enj; + } + + public void setRepv2(BigDecimal repv2) { + this.repv2 = repv2; + } + + public void setBand(BigDecimal band) { + this.band = band; + } + + public void setNmr(BigDecimal nmr) { + this.nmr = nmr; + } + + public void setCgld(BigDecimal cgld) { + this.cgld = cgld; + } + + public void setUma(BigDecimal uma) { + this.uma = uma; + } + + public void setLrc(BigDecimal lrc) { + this.lrc = lrc; + } + + public void setYfi(BigDecimal yfi) { + this.yfi = yfi; + } + + public void setUni(BigDecimal uni) { + this.uni = uni; + } + + public void setBal(BigDecimal bal) { + this.bal = bal; + } + + public void setRen(BigDecimal ren) { + this.ren = ren; + } + + public void setWbtc(BigDecimal wbtc) { + this.wbtc = wbtc; + } + + public void setNu(BigDecimal nu) { + this.nu = nu; + } + + public void setYfii(BigDecimal yfii) { + this.yfii = yfii; + } + + public void setFil(BigDecimal fil) { + this.fil = fil; + } + + public void setAave(BigDecimal aave) { + this.aave = aave; + } + + public void setBnt(BigDecimal bnt) { + this.bnt = bnt; + } + + public void setGrt(BigDecimal grt) { + this.grt = grt; + } + + public void setSnx(BigDecimal snx) { + this.snx = snx; + } + + public void setStorj(BigDecimal storj) { + this.storj = storj; + } + + public void setSushi(BigDecimal sushi) { + this.sushi = sushi; + } + + public void setMatic(BigDecimal matic) { + this.matic = matic; + } + + public void setSkl(BigDecimal skl) { + this.skl = skl; + } + + public void setAda(BigDecimal ada) { + this.ada = ada; + } + + public void setAnkr(BigDecimal ankr) { + this.ankr = ankr; + } + + public void setCrv(BigDecimal crv) { + this.crv = crv; + } + + public void setIcp(BigDecimal icp) { + this.icp = icp; + } + + public void setNkn(BigDecimal nkn) { + this.nkn = nkn; + } + + public void setOgn(BigDecimal ogn) { + this.ogn = ogn; + } + + public void setUsdt(BigDecimal usdt) { + this.usdt = usdt; + } + + public void setForth(BigDecimal forth) { + this.forth = forth; + } + + public void setCtsi(BigDecimal ctsi) { + this.ctsi = ctsi; + } + + public void setTrb(BigDecimal trb) { + this.trb = trb; + } + + public void setPoly(BigDecimal poly) { + this.poly = poly; + } + + public void setMir(BigDecimal mir) { + this.mir = mir; + } + + public void setRlc(BigDecimal rlc) { + this.rlc = rlc; + } + + public void setDot(BigDecimal dot) { + this.dot = dot; + } + + public void setSol(BigDecimal sol) { + this.sol = sol; + } + + public void setDoge(BigDecimal doge) { + this.doge = doge; + } + + public void setMln(BigDecimal mln) { + this.mln = mln; + } + + public void setGtc(BigDecimal gtc) { + this.gtc = gtc; + } + + public void setAmp(BigDecimal amp) { + this.amp = amp; + } + + public void setShib(BigDecimal shib) { + this.shib = shib; + } + + public void setChz(BigDecimal chz) { + this.chz = chz; + } + + public void setKeep(BigDecimal keep) { + this.keep = keep; + } + + public void setLpt(BigDecimal lpt) { + this.lpt = lpt; + } + + public void setQnt(BigDecimal qnt) { + this.qnt = qnt; + } + + public void setBond(BigDecimal bond) { + this.bond = bond; + } + + public void setRly(BigDecimal rly) { + this.rly = rly; + } + + public void setClv(BigDecimal clv) { + this.clv = clv; + } + + public void setFarm(BigDecimal farm) { + this.farm = farm; + } + + public void setMask(BigDecimal mask) { + this.mask = mask; + } + + public void setFet(BigDecimal fet) { + this.fet = fet; + } + + public void setPax(BigDecimal pax) { + this.pax = pax; + } + + public void setAch(BigDecimal ach) { + this.ach = ach; + } + + public void setAsm(BigDecimal asm) { + this.asm = asm; + } + + public void setPla(BigDecimal pla) { + this.pla = pla; + } + + public void setRai(BigDecimal rai) { + this.rai = rai; + } + + public void setTribe(BigDecimal tribe) { + this.tribe = tribe; + } + + public void setOrn(BigDecimal orn) { + this.orn = orn; + } + + public void setIotx(BigDecimal iotx) { + this.iotx = iotx; + } + + public void setUst(BigDecimal ust) { + this.ust = ust; + } + + public void setQuick(BigDecimal quick) { + this.quick = quick; + } + + public void setAxs(BigDecimal axs) { + this.axs = axs; + } + + public void setReq(BigDecimal req) { + this.req = req; + } + + public void setWluna(BigDecimal wluna) { + this.wluna = wluna; + } + + public void setTru(BigDecimal tru) { + this.tru = tru; + } + + public void setRad(BigDecimal rad) { + this.rad = rad; + } + + public void setCoti(BigDecimal coti) { + this.coti = coti; + } + + public void setDdx(BigDecimal ddx) { + this.ddx = ddx; + } + + public void setSuku(BigDecimal suku) { + this.suku = suku; + } + + public void setRgt(BigDecimal rgt) { + this.rgt = rgt; + } + + public void setXyo(BigDecimal xyo) { + this.xyo = xyo; + } + + public void setZen(BigDecimal zen) { + this.zen = zen; + } + + public void setAuction(BigDecimal auction) { + this.auction = auction; + } + + public void setBusd(BigDecimal busd) { + this.busd = busd; + } + + public void setJasmy(BigDecimal jasmy) { + this.jasmy = jasmy; + } + + public void setWcfg(BigDecimal wcfg) { + this.wcfg = wcfg; + } + + public void setBtrst(BigDecimal btrst) { + this.btrst = btrst; + } + + public void setAgld(BigDecimal agld) { + this.agld = agld; + } + + public void setAvax(BigDecimal avax) { + this.avax = avax; + } + + public void setFx(BigDecimal fx) { + this.fx = fx; + } + + public void setTrac(BigDecimal trac) { + this.trac = trac; + } + + public void setLcx(BigDecimal lcx) { + this.lcx = lcx; + } + + public void setArpa(BigDecimal arpa) { + this.arpa = arpa; + } + + public void setBadger(BigDecimal badger) { + this.badger = badger; + } + + public void setKrl(BigDecimal krl) { + this.krl = krl; + } + + public void setPerp(BigDecimal perp) { + this.perp = perp; + } + + public void setRari(BigDecimal rari) { + this.rari = rari; + } + + public void setDeso(BigDecimal deso) { + this.deso = deso; + } + + public void setApi3(BigDecimal api3) { + this.api3 = api3; + } + + public void setNct(BigDecimal nct) { + this.nct = nct; + } + + public void setShping(BigDecimal shping) { + this.shping = shping; + } + + public void setUpi(BigDecimal upi) { + this.upi = upi; + } + + public void setCro(BigDecimal cro) { + this.cro = cro; + } + + public void setAvt(BigDecimal avt) { + this.avt = avt; + } + + public void setMdt(BigDecimal mdt) { + this.mdt = mdt; + } + + public void setVgx(BigDecimal vgx) { + this.vgx = vgx; + } + + public void setAlcx(BigDecimal alcx) { + this.alcx = alcx; + } + + public void setCoval(BigDecimal coval) { + this.coval = coval; + } + + public void setFox(BigDecimal fox) { + this.fox = fox; + } + + public void setMusd(BigDecimal musd) { + this.musd = musd; + } + + public void setGala(BigDecimal gala) { + this.gala = gala; + } + + public void setPowr(BigDecimal powr) { + this.powr = powr; + } + + public void setGyen(BigDecimal gyen) { + this.gyen = gyen; + } + + public void setAlice(BigDecimal alice) { + this.alice = alice; + } + + public void setInv(BigDecimal inv) { + this.inv = inv; + } + + public void setLqty(BigDecimal lqty) { + this.lqty = lqty; + } + + public void setPro(BigDecimal pro) { + this.pro = pro; + } + + public void setSpell(BigDecimal spell) { + this.spell = spell; + } + + public void setEns(BigDecimal ens) { + this.ens = ens; + } + + public void setDia(BigDecimal dia) { + this.dia = dia; + } + + public void setBlz(BigDecimal blz) { + this.blz = blz; + } + + public void setCtx(BigDecimal ctx) { + this.ctx = ctx; + } + + public void setIdex(BigDecimal idex) { + this.idex = idex; + } + + public void setMco2(BigDecimal mco2) { + this.mco2 = mco2; + } + + public void setPols(BigDecimal pols) { + this.pols = pols; + } + + public void setUnfi(BigDecimal unfi) { + this.unfi = unfi; + } + + public void setStx(BigDecimal stx) { + this.stx = stx; + } + + public void setGods(BigDecimal gods) { + this.gods = gods; + } + + public void setImx(BigDecimal imx) { + this.imx = imx; + } + + public void setRbn(BigDecimal rbn) { + this.rbn = rbn; + } + + public void setBico(BigDecimal bico) { + this.bico = bico; + } + + public void setGfi(BigDecimal gfi) { + this.gfi = gfi; + } + + public void setGlm(BigDecimal glm) { + this.glm = glm; + } + + public void setMpl(BigDecimal mpl) { + this.mpl = mpl; + } + + public void setPlu(BigDecimal plu) { + this.plu = plu; + } + + public void setFida(BigDecimal fida) { + this.fida = fida; + } + + public void setOrca(BigDecimal orca) { + this.orca = orca; + } + + public void setCrpt(BigDecimal crpt) { + this.crpt = crpt; + } + + public void setQsp(BigDecimal qsp) { + this.qsp = qsp; + } + + public void setRndr(BigDecimal rndr) { + this.rndr = rndr; + } + + public void setSyn(BigDecimal syn) { + this.syn = syn; + } + + public void setAioz(BigDecimal aioz) { + this.aioz = aioz; + } + + public void setAergo(BigDecimal aergo) { + this.aergo = aergo; + } + + public void setHigh(BigDecimal high) { + this.high = high; + } + + public void setRose(BigDecimal rose) { + this.rose = rose; + } + + public void setApe(BigDecimal ape) { + this.ape = ape; + } + + public void setMina(BigDecimal mina) { + this.mina = mina; + } + + public void setGmt(BigDecimal gmt) { + this.gmt = gmt; + } + + public void setGst(BigDecimal gst) { + this.gst = gst; + } + + public void setGal(BigDecimal gal) { + this.gal = gal; + } + + public BigDecimal getUsdt() { + return usdt; + } + + public BigDecimal getForth() { + return forth; + } + + public BigDecimal getCtsi() { + return ctsi; + } + + public BigDecimal getTrb() { + return trb; + } + + public BigDecimal getPoly() { + return poly; + } + + public BigDecimal getMir() { + return mir; + } + + public BigDecimal getRlc() { + return rlc; + } + + public BigDecimal getDot() { + return dot; + } + + public BigDecimal getSol() { + return sol; + } + + public BigDecimal getDoge() { + return doge; + } + + public BigDecimal getMln() { + return mln; + } + + public BigDecimal getGtc() { + return gtc; + } + + public BigDecimal getAmp() { + return amp; + } + + public BigDecimal getShib() { + return shib; + } + + public BigDecimal getChz() { + return chz; + } + + public BigDecimal getKeep() { + return keep; + } + + public BigDecimal getLpt() { + return lpt; + } + + public BigDecimal getQnt() { + return qnt; + } + + public BigDecimal getBond() { + return bond; + } + + public BigDecimal getRly() { + return rly; + } + + public BigDecimal getClv() { + return clv; + } + + public BigDecimal getFarm() { + return farm; + } + + public BigDecimal getMask() { + return mask; + } + + public BigDecimal getFet() { + return fet; + } + + public BigDecimal getPax() { + return pax; + } + + public BigDecimal getAch() { + return ach; + } + + public BigDecimal getAsm() { + return asm; + } + + public BigDecimal getPla() { + return pla; + } + + public BigDecimal getRai() { + return rai; + } + + public BigDecimal getTribe() { + return tribe; + } + + public BigDecimal getOrn() { + return orn; + } + + public BigDecimal getIotx() { + return iotx; + } + + public BigDecimal getUst() { + return ust; + } + + public BigDecimal getQuick() { + return quick; + } + + public BigDecimal getAxs() { + return axs; + } + + public BigDecimal getReq() { + return req; + } + + public BigDecimal getWluna() { + return wluna; + } + + public BigDecimal getTru() { + return tru; + } + + public BigDecimal getRad() { + return rad; + } + + public BigDecimal getCoti() { + return coti; + } + + public BigDecimal getDdx() { + return ddx; + } + + public BigDecimal getSuku() { + return suku; + } + + public BigDecimal getRgt() { + return rgt; + } + + public BigDecimal getXyo() { + return xyo; + } + + public BigDecimal getZen() { + return zen; + } + + public BigDecimal getAuction() { + return auction; + } + + public BigDecimal getBusd() { + return busd; + } + + public BigDecimal getJasmy() { + return jasmy; + } + + public BigDecimal getWcfg() { + return wcfg; + } + + public BigDecimal getBtrst() { + return btrst; + } + + public BigDecimal getAgld() { + return agld; + } + + public BigDecimal getAvax() { + return avax; + } + + public BigDecimal getFx() { + return fx; + } + + public BigDecimal getTrac() { + return trac; + } + + public BigDecimal getLcx() { + return lcx; + } + + public BigDecimal getArpa() { + return arpa; + } + + public BigDecimal getBadger() { + return badger; + } + + public BigDecimal getKrl() { + return krl; + } + + public BigDecimal getPerp() { + return perp; + } + + public BigDecimal getRari() { + return rari; + } + + public BigDecimal getDeso() { + return deso; + } + + public BigDecimal getApi3() { + return api3; + } + + public BigDecimal getNct() { + return nct; + } + + public BigDecimal getShping() { + return shping; + } + + public BigDecimal getUpi() { + return upi; + } + + public BigDecimal getCro() { + return cro; + } + + public BigDecimal getAvt() { + return avt; + } + + public BigDecimal getMdt() { + return mdt; + } + + public BigDecimal getVgx() { + return vgx; + } + + public BigDecimal getAlcx() { + return alcx; + } + + public BigDecimal getCoval() { + return coval; + } + + public BigDecimal getFox() { + return fox; + } + + public BigDecimal getMusd() { + return musd; + } + + public BigDecimal getGala() { + return gala; + } + + public BigDecimal getPowr() { + return powr; + } + + public BigDecimal getGyen() { + return gyen; + } + + public BigDecimal getAlice() { + return alice; + } + + public BigDecimal getInv() { + return inv; + } + + public BigDecimal getLqty() { + return lqty; + } + + public BigDecimal getPro() { + return pro; + } + + public BigDecimal getSpell() { + return spell; + } + + public BigDecimal getEns() { + return ens; + } + + public BigDecimal getDia() { + return dia; + } + + public BigDecimal getBlz() { + return blz; + } + + public BigDecimal getCtx() { + return ctx; + } + + public BigDecimal getIdex() { + return idex; + } + + public BigDecimal getMco2() { + return mco2; + } + + public BigDecimal getPols() { + return pols; + } + + public BigDecimal getUnfi() { + return unfi; + } + + public BigDecimal getStx() { + return stx; + } + + public BigDecimal getGods() { + return gods; + } + + public BigDecimal getImx() { + return imx; + } + + public BigDecimal getRbn() { + return rbn; + } + + public BigDecimal getBico() { + return bico; + } + + public BigDecimal getGfi() { + return gfi; + } + + public BigDecimal getGlm() { + return glm; + } + + public BigDecimal getMpl() { + return mpl; + } + + public BigDecimal getPlu() { + return plu; + } + + public BigDecimal getFida() { + return fida; + } + + public BigDecimal getOrca() { + return orca; + } + + public BigDecimal getCrpt() { + return crpt; + } + + public BigDecimal getQsp() { + return qsp; + } + + public BigDecimal getRndr() { + return rndr; + } + + public BigDecimal getSyn() { + return syn; + } + + public BigDecimal getAioz() { + return aioz; + } + + public BigDecimal getAergo() { + return aergo; + } + + public BigDecimal getHigh() { + return high; + } + + public BigDecimal getRose() { + return rose; + } + + public BigDecimal getApe() { + return ape; + } + + public BigDecimal getMina() { + return mina; + } + + public BigDecimal getGmt() { + return gmt; + } + + public BigDecimal getGst() { + return gst; + } + + public BigDecimal getGal() { + return gal; + } + + public BigDecimal getSuper() { + return super1; + } + + public BigDecimal getMana() { + return mana; + } + + public BigDecimal getKnc() { + return knc; + } + + public BigDecimal getLink() { + return link; + } + + public BigDecimal getMkr() { + return mkr; + } + + public BigDecimal getCvc() { + return cvc; + } + + public BigDecimal getOmg() { + return omg; + } + + public BigDecimal getGnt() { + return gnt; + } + + public BigDecimal getDai() { + return dai; + } + + public BigDecimal getSnt() { + return snt; + } + + public BigDecimal getZec() { + return zec; + } + + public BigDecimal getXrp() { + return xrp; + } + + public BigDecimal getRep() { + return rep; + } + + public BigDecimal getXlm() { + return xlm; + } + + public BigDecimal getEos() { + return eos; + } + + public BigDecimal getXtz() { + return xtz; + } + + public BigDecimal getAlgo() { + return algo; + } + + public BigDecimal getDash() { + return dash; + } + + public BigDecimal getAtom() { + return atom; + } + + public BigDecimal getOxt() { + return oxt; + } + + public BigDecimal getComp() { + return comp; + } + + public BigDecimal getEnj() { + return enj; + } + + public BigDecimal getRepv2() { + return repv2; + } + + public BigDecimal getBand() { + return band; + } + + public BigDecimal getNmr() { + return nmr; + } + + public BigDecimal getCgld() { + return cgld; + } + + public BigDecimal getUma() { + return uma; + } + + public BigDecimal getLrc() { + return lrc; + } + + public BigDecimal getYfi() { + return yfi; + } + + public BigDecimal getUni() { + return uni; + } + + public BigDecimal getBal() { + return bal; + } + + public BigDecimal getRen() { + return ren; + } + + public BigDecimal getWbtc() { + return wbtc; + } + + public BigDecimal getNu() { + return nu; + } + + public BigDecimal getYfii() { + return yfii; + } + + public BigDecimal getFil() { + return fil; + } + + public BigDecimal getAave() { + return aave; + } + + public BigDecimal getBnt() { + return bnt; + } + + public BigDecimal getGrt() { + return grt; + } + + public BigDecimal getSnx() { + return snx; + } + + public BigDecimal getStorj() { + return storj; + } + + public BigDecimal getSushi() { + return sushi; + } + + public BigDecimal getMatic() { + return matic; + } + + public BigDecimal getSkl() { + return skl; + } + + public BigDecimal getAda() { + return ada; + } + + public BigDecimal getAnkr() { + return ankr; + } + + public BigDecimal getCrv() { + return crv; + } + + public BigDecimal getIcp() { + return icp; + } + + public BigDecimal getNkn() { + return nkn; + } + + public BigDecimal getOgn() { + return ogn; + } + + public BigDecimal get1inch() { + return this.inch1; + } + + public BigDecimal getLoom() { + return loom; + } + + public BigDecimal getBat() { + return bat; + } + + public BigDecimal getUsdc() { + return usdc; + } + + public BigDecimal getZrx() { + return zrx; + } + + public BigDecimal getEtc() { + return etc; + } + + public BigDecimal getEth2() { + return eth2; + } + + public BigDecimal getBsv() { + return bsv; + } + + public BigDecimal getBch() { + return bch; + } + + public BigDecimal getCnh() { + return cnh; + } + + public BigDecimal getZwd() { + return zwd; + } + + public BigDecimal getGbx() { + return gbx; + } + + public BigDecimal getXts() { + return xts; + } + + public BigDecimal getVes() { + return ves; + } + + public BigDecimal getXba() { + return xba; + } + + public BigDecimal getAed() { + return aed; + } + + public BigDecimal getAfn() { + return afn; + } + + public BigDecimal getAll() { + return all; + } + + public BigDecimal getAmd() { + return amd; + } + + public BigDecimal getAng() { + return ang; + } + + public BigDecimal getAoa() { + return aoa; + } + + public BigDecimal getArs() { + return ars; + } + + public BigDecimal getAud() { + return aud; + } + + public BigDecimal getAwg() { + return awg; + } + + public BigDecimal getAzn() { + return azn; + } + + public BigDecimal getBam() { + return bam; + } + + public BigDecimal getBbd() { + return bbd; + } + + public BigDecimal getBdt() { + return bdt; + } + + public BigDecimal getBgn() { + return bgn; + } + + public BigDecimal getBhd() { + return bhd; + } + + public BigDecimal getBif() { + return bif; + } + + public BigDecimal getBmd() { + return bmd; + } + + public BigDecimal getBnd() { + return bnd; + } + + public BigDecimal getBob() { + return bob; + } + + public BigDecimal getBrl() { + return brl; + } + + public BigDecimal getBsd() { + return bsd; + } + + public BigDecimal getBtc() { + return btc; + } + + public BigDecimal getBtn() { + return btn; + } + + public BigDecimal getBwp() { + return bwp; + } + + public BigDecimal getByn() { + return byn; + } + + public BigDecimal getByr() { + return byr; + } + + public BigDecimal getBzd() { + return bzd; + } + + public BigDecimal getCad() { + return cad; + } + + public BigDecimal getCdf() { + return cdf; + } + + public BigDecimal getChf() { + return chf; + } + + public BigDecimal getClf() { + return clf; + } + + public BigDecimal getClp() { + return clp; + } + + public BigDecimal getCny() { + return cny; + } + + public BigDecimal getCop() { + return cop; + } + + public BigDecimal getCrc() { + return crc; + } + + public BigDecimal getCuc() { + return cuc; + } + + public BigDecimal getCve() { + return cve; + } + + public BigDecimal getCzk() { + return czk; + } + + public BigDecimal getDjf() { + return djf; + } + + public BigDecimal getDkk() { + return dkk; + } + + public BigDecimal getDop() { + return dop; + } + + public BigDecimal getDzd() { + return dzd; + } + + public BigDecimal getEek() { + return eek; + } + + public BigDecimal getEgp() { + return egp; + } + + public BigDecimal getErn() { + return ern; + } + + public BigDecimal getEtb() { + return etb; + } + + public BigDecimal getEth() { + return eth; + } + + public BigDecimal getEur() { + return eur; + } + + public BigDecimal getFjd() { + return fjd; + } + + public BigDecimal getFkp() { + return fkp; + } + + public BigDecimal getGbp() { + return gbp; + } + + public BigDecimal getGel() { + return gel; + } + + public BigDecimal getGgp() { + return ggp; + } + + public BigDecimal getGhs() { + return ghs; + } + + public BigDecimal getGip() { + return gip; + } + + public BigDecimal getGmd() { + return gmd; + } + + public BigDecimal getGnf() { + return gnf; + } + + public BigDecimal getGtq() { + return gtq; + } + + public BigDecimal getGyd() { + return gyd; + } + + public BigDecimal getHkd() { + return hkd; + } + + public BigDecimal getHnl() { + return hnl; + } + + public BigDecimal getHrk() { + return hrk; + } + + public BigDecimal getHtg() { + return htg; + } + + public BigDecimal getHuf() { + return huf; + } + + public BigDecimal getIdr() { + return idr; + } + + public BigDecimal getIls() { + return ils; + } + + public BigDecimal getImp() { + return imp; + } + + public BigDecimal getInr() { + return inr; + } + + public BigDecimal getIqd() { + return iqd; + } + + public BigDecimal getIsk() { + return isk; + } + + public BigDecimal getJep() { + return jep; + } + + public BigDecimal getJmd() { + return jmd; + } + + public BigDecimal getJod() { + return jod; + } + + public BigDecimal getJpy() { + return jpy; + } + + public BigDecimal getKes() { + return kes; + } + + public BigDecimal getKgs() { + return kgs; + } + + public BigDecimal getKhr() { + return khr; + } + + public BigDecimal getKmf() { + return kmf; + } + + public BigDecimal getKrw() { + return krw; + } + + public BigDecimal getKwd() { + return kwd; + } + + public BigDecimal getKyd() { + return kyd; + } + + public BigDecimal getKzt() { + return kzt; + } + + public BigDecimal getLak() { + return lak; + } + + public BigDecimal getLbp() { + return lbp; + } + + public BigDecimal getLkr() { + return lkr; + } + + public BigDecimal getLrd() { + return lrd; + } + + public BigDecimal getLsl() { + return lsl; + } + + public BigDecimal getLtc() { + return ltc; + } + + public BigDecimal getLtl() { + return ltl; + } + + public BigDecimal getLvl() { + return lvl; + } + + public BigDecimal getLyd() { + return lyd; + } + + public BigDecimal getMad() { + return mad; + } + + public BigDecimal getMdl() { + return mdl; + } + + public BigDecimal getMga() { + return mga; + } + + public BigDecimal getMkd() { + return mkd; + } + + public BigDecimal getMmk() { + return mmk; + } + + public BigDecimal getMnt() { + return mnt; + } + + public BigDecimal getMop() { + return mop; + } + + public BigDecimal getMro() { + return mro; + } + + public BigDecimal getMtl() { + return mtl; + } + + public BigDecimal getMur() { + return mur; + } + + public BigDecimal getMvr() { + return mvr; + } + + public BigDecimal getMwk() { + return mwk; + } + + public BigDecimal getMxn() { + return mxn; + } + + public BigDecimal getMyr() { + return myr; + } + + public BigDecimal getMzn() { + return mzn; + } + + public BigDecimal getNad() { + return nad; + } + + public BigDecimal getNgn() { + return ngn; + } + + public BigDecimal getNio() { + return nio; + } + + public BigDecimal getNok() { + return nok; + } + + public BigDecimal getNpr() { + return npr; + } + + public BigDecimal getNzd() { + return nzd; + } + + public BigDecimal getOmr() { + return omr; + } + + public BigDecimal getPab() { + return pab; + } + + public BigDecimal getPen() { + return pen; + } + + public BigDecimal getPgk() { + return pgk; + } + + public BigDecimal getPhp() { + return php; + } + + public BigDecimal getPkr() { + return pkr; + } + + public BigDecimal getPln() { + return pln; + } + + public BigDecimal getPyg() { + return pyg; + } + + public BigDecimal getQar() { + return qar; + } + + public BigDecimal getRon() { + return ron; + } + + public BigDecimal getRsd() { + return rsd; + } + + public BigDecimal getRub() { + return rub; + } + + public BigDecimal getRwf() { + return rwf; + } + + public BigDecimal getSar() { + return sar; + } + + public BigDecimal getSbd() { + return sbd; + } + + public BigDecimal getScr() { + return scr; + } + + public BigDecimal getSek() { + return sek; + } + + public BigDecimal getSgd() { + return sgd; + } + + public BigDecimal getShp() { + return shp; + } + + public BigDecimal getSll() { + return sll; + } + + public BigDecimal getSos() { + return sos; + } + + public BigDecimal getSrd() { + return srd; + } + + public BigDecimal getSsp() { + return ssp; + } + + public BigDecimal getStd() { + return std; + } + + public BigDecimal getSvc() { + return svc; + } + + public BigDecimal getSzl() { + return szl; + } + + public BigDecimal getThb() { + return thb; + } + + public BigDecimal getTjs() { + return tjs; + } + + public BigDecimal getTmt() { + return tmt; + } + + public BigDecimal getTnd() { + return tnd; + } + + public BigDecimal getTop() { + return top; + } + + public BigDecimal getTry1() { + return try1; + } + + public BigDecimal getTtd() { + return ttd; + } + + public BigDecimal getTwd() { + return twd; + } + + public BigDecimal getTzs() { + return tzs; + } + + public BigDecimal getUah() { + return uah; + } + + public BigDecimal getUgx() { + return ugx; + } + + public BigDecimal getUsd() { + return usd; + } + + public BigDecimal getUyu() { + return uyu; + } + + public BigDecimal getUzs() { + return uzs; + } + + public BigDecimal getVef() { + return vef; + } + + public BigDecimal getVnd() { + return vnd; + } + + public BigDecimal getVuv() { + return vuv; + } + + public BigDecimal getWst() { + return wst; + } + + public BigDecimal getXaf() { + return xaf; + } + + public BigDecimal getXag() { + return xag; + } + + public BigDecimal getXau() { + return xau; + } + + public BigDecimal getXcd() { + return xcd; + } + + public BigDecimal getXdr() { + return xdr; + } + + public BigDecimal getXof() { + return xof; + } + + public BigDecimal getXpd() { + return xpd; + } + + public BigDecimal getXpf() { + return xpf; + } + + public BigDecimal getXpt() { + return xpt; + } + + public BigDecimal getYer() { + return yer; + } + + public BigDecimal getZar() { + return zar; + } + + public BigDecimal getZmk() { + return zmk; + } + + public BigDecimal getZmw() { + return zmw; + } + + public BigDecimal getZwl() { + return zwl; + } + + public BigDecimal getFlow() { + return flow; + } + + public void setFlow(BigDecimal flow) { + this.flow = flow; + } + + public BigDecimal getDnt() { + return dnt; + } + + public void setDnt(BigDecimal dnt) { + this.dnt = dnt; + } + + public BigDecimal getSand() { + return sand; + } + + public void setSand(BigDecimal sand) { + this.sand = sand; + } + + public ObjectId get_id() { + return _id; + } + + public void set_id(ObjectId _id) { + this._id = _id; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public BigDecimal getKsm() { + return ksm; + } + + public void setKsm(BigDecimal ksm) { + this.ksm = ksm; + } + + public BigDecimal getAta() { + return ata; + } + + public void setAta(BigDecimal ata) { + this.ata = ata; + } + + public BigDecimal getPrq() { + return prq; + } + + public void setPrq(BigDecimal prq) { + this.prq = prq; + } + + public BigDecimal getHopr() { + return hopr; + } + + public void setHopr(BigDecimal hopr) { + this.hopr = hopr; + } + + public BigDecimal getJup() { + return jup; + } + + public void setJup(BigDecimal jup) { + this.jup = jup; + } + + public BigDecimal getMath() { + return math; + } + + public void setMath(BigDecimal math) { + this.math = math; + } + + public BigDecimal getWampl() { + return wampl; + } + + public void setWampl(BigDecimal wampl) { + this.wampl = wampl; + } + + public BigDecimal getIndex() { + return index; + } + + public void setIndex(BigDecimal index) { + this.index = index; + } + + public BigDecimal getMuse() { + return muse; + } + + public void setMuse(BigDecimal muse) { + this.muse = muse; + } + + public BigDecimal getDrep() { + return drep; + } + + public void setDrep(BigDecimal drep) { + this.drep = drep; + } + + public BigDecimal getEla() { + return ela; + } + + public void setEla(BigDecimal ela) { + this.ela = ela; + } + + public BigDecimal getFort() { + return fort; + } + + public void setFort(BigDecimal fort) { + this.fort = fort; + } + + public BigDecimal getDext() { + return dext; + } + + public void setDext(BigDecimal dext) { + this.dext = dext; + } + + public BigDecimal getAleph() { + return aleph; + } + + public void setAleph(BigDecimal aleph) { + this.aleph = aleph; + } + + public BigDecimal getFis() { + return fis; + } + + public void setFis(BigDecimal fis) { + this.fis = fis; + } + + public BigDecimal getBit() { + return bit; + } + + public void setBit(BigDecimal bit) { + this.bit = bit; + } + + public BigDecimal getC98() { + return c98; + } + + public void setC98(BigDecimal c98) { + this.c98 = c98; + } + + public BigDecimal getDar() { + return dar; + } + + public void setDar(BigDecimal dar) { + this.dar = dar; + } + + public BigDecimal getTime() { + return time; + } + + public void setTime(BigDecimal time) { + this.time = time; + } + + public BigDecimal getPond() { + return pond; + } + + public void setPond(BigDecimal pond) { + this.pond = pond; + } + + public BigDecimal getDyp() { + return dyp; + } + + public void setDyp(BigDecimal dyp) { + this.dyp = dyp; + } + + public BigDecimal getAst() { + return ast; + } + + public void setAst(BigDecimal ast) { + this.ast = ast; + } + + public BigDecimal getGusd() { + return gusd; + } + + public void setGusd(BigDecimal gusd) { + this.gusd = gusd; + } + + public BigDecimal getMedia() { + return media; + } + + public void setMedia(BigDecimal media) { + this.media = media; + } + + public BigDecimal getMona() { + return mona; + } + + public void setMona(BigDecimal mona) { + this.mona = mona; + } + + public BigDecimal getBoba() { + return boba; + } + + public void setBoba(BigDecimal boba) { + this.boba = boba; + } + + public BigDecimal getMetis() { + return metis; + } + + public void setMetis(BigDecimal metis) { + this.metis = metis; + } + + public BigDecimal getXcn() { + return xcn; + } + + public void setXcn(BigDecimal xcn) { + this.xcn = xcn; + } + + @Override + public String toString() { + return "QuoteCb [_id=" + _id + ", createdAt=" + createdAt + ", xcn=" + xcn + ", metis=" + metis + ", boba=" + + boba + ", mona=" + mona + ", media=" + media + ", gusd=" + gusd + ", ast=" + ast + ", dyp=" + dyp + + ", pond=" + pond + ", time=" + time + ", dar=" + dar + ", c98=" + c98 + ", bit=" + bit + ", fis=" + + fis + ", dext=" + dext + ", aleph=" + aleph + ", fort=" + fort + ", ela=" + ela + ", drep=" + drep + + ", muse=" + muse + ", index=" + index + ", wampl=" + wampl + ", math=" + math + ", jup=" + jup + + ", hopr=" + hopr + ", aed=" + aed + ", prq=" + prq + ", ata=" + ata + ", afn=" + afn + ", all=" + all + + ", amd=" + amd + ", ang=" + ang + ", aoa=" + aoa + ", ars=" + ars + ", aud=" + aud + ", awg=" + awg + + ", azn=" + azn + ", bam=" + bam + ", bbd=" + bbd + ", bdt=" + bdt + ", bgn=" + bgn + ", bhd=" + bhd + + ", bif=" + bif + ", bmd=" + bmd + ", bnd=" + bnd + ", bob=" + bob + ", brl=" + brl + ", bsd=" + bsd + + ", btc=" + btc + ", btn=" + btn + ", bwp=" + bwp + ", byn=" + byn + ", byr=" + byr + ", bzd=" + bzd + + ", cad=" + cad + ", cdf=" + cdf + ", chf=" + chf + ", clf=" + clf + ", clp=" + clp + ", cny=" + cny + + ", cop=" + cop + ", crc=" + crc + ", cuc=" + cuc + ", cve=" + cve + ", czk=" + czk + ", djf=" + djf + + ", dkk=" + dkk + ", dop=" + dop + ", dzd=" + dzd + ", eek=" + eek + ", egp=" + egp + ", ern=" + ern + + ", etb=" + etb + ", eth=" + eth + ", eur=" + eur + ", fjd=" + fjd + ", fkp=" + fkp + ", gbp=" + gbp + + ", gel=" + gel + ", ggp=" + ggp + ", ghs=" + ghs + ", gip=" + gip + ", gmd=" + gmd + ", gnf=" + gnf + + ", gtq=" + gtq + ", gyd=" + gyd + ", hkd=" + hkd + ", hnl=" + hnl + ", hrk=" + hrk + ", htg=" + htg + + ", huf=" + huf + ", idr=" + idr + ", ils=" + ils + ", imp=" + imp + ", inr=" + inr + ", iqd=" + iqd + + ", isk=" + isk + ", jep=" + jep + ", jmd=" + jmd + ", jod=" + jod + ", jpy=" + jpy + ", kes=" + kes + + ", kgs=" + kgs + ", khr=" + khr + ", kmf=" + kmf + ", krw=" + krw + ", kwd=" + kwd + ", kyd=" + kyd + + ", kzt=" + kzt + ", lak=" + lak + ", lbp=" + lbp + ", lkr=" + lkr + ", lrd=" + lrd + ", lsl=" + lsl + + ", ltc=" + ltc + ", ltl=" + ltl + ", lvl=" + lvl + ", lyd=" + lyd + ", mad=" + mad + ", mdl=" + mdl + + ", mga=" + mga + ", mkd=" + mkd + ", mmk=" + mmk + ", mnt=" + mnt + ", mop=" + mop + ", mro=" + mro + + ", mtl=" + mtl + ", mur=" + mur + ", mvr=" + mvr + ", mwk=" + mwk + ", mxn=" + mxn + ", myr=" + myr + + ", mzn=" + mzn + ", nad=" + nad + ", ngn=" + ngn + ", nio=" + nio + ", nok=" + nok + ", npr=" + npr + + ", nzd=" + nzd + ", omr=" + omr + ", pab=" + pab + ", pen=" + pen + ", pgk=" + pgk + ", php=" + php + + ", pkr=" + pkr + ", pln=" + pln + ", pyg=" + pyg + ", qar=" + qar + ", ron=" + ron + ", rsd=" + rsd + + ", rub=" + rub + ", rwf=" + rwf + ", sar=" + sar + ", sbd=" + sbd + ", scr=" + scr + ", sek=" + sek + + ", sgd=" + sgd + ", shp=" + shp + ", sll=" + sll + ", sos=" + sos + ", srd=" + srd + ", ssp=" + ssp + + ", std=" + std + ", svc=" + svc + ", szl=" + szl + ", thb=" + thb + ", tjs=" + tjs + ", tmt=" + tmt + + ", tnd=" + tnd + ", top=" + top + ", try1=" + try1 + ", ttd=" + ttd + ", twd=" + twd + ", tzs=" + tzs + + ", uah=" + uah + ", ugx=" + ugx + ", usd=" + usd + ", uyu=" + uyu + ", uzs=" + uzs + ", vef=" + vef + + ", vnd=" + vnd + ", vuv=" + vuv + ", wst=" + wst + ", xaf=" + xaf + ", xag=" + xag + ", xau=" + xau + + ", xcd=" + xcd + ", xdr=" + xdr + ", xof=" + xof + ", xpd=" + xpd + ", xpf=" + xpf + ", xpt=" + xpt + + ", yer=" + yer + ", zar=" + zar + ", zmk=" + zmk + ", zmw=" + zmw + ", zwl=" + zwl + ", ves=" + ves + + ", xba=" + xba + ", xts=" + xts + ", gbx=" + gbx + ", cnh=" + cnh + ", zwd=" + zwd + ", bch=" + bch + + ", bsv=" + bsv + ", eth2=" + eth2 + ", etc=" + etc + ", zrx=" + zrx + ", usdc=" + usdc + ", bat=" + + bat + ", loom=" + loom + ", mana=" + mana + ", knc=" + knc + ", link=" + link + ", mkr=" + mkr + + ", cvc=" + cvc + ", omg=" + omg + ", gnt=" + gnt + ", dai=" + dai + ", snt=" + snt + ", zec=" + zec + + ", xrp=" + xrp + ", rep=" + rep + ", xlm=" + xlm + ", eos=" + eos + ", xtz=" + xtz + ", algo=" + algo + + ", dash=" + dash + ", atom=" + atom + ", oxt=" + oxt + ", comp=" + comp + ", enj=" + enj + ", repv2=" + + repv2 + ", band=" + band + ", nmr=" + nmr + ", cgld=" + cgld + ", uma=" + uma + ", lrc=" + lrc + + ", yfi=" + yfi + ", uni=" + uni + ", bal=" + bal + ", ren=" + ren + ", wbtc=" + wbtc + ", nu=" + nu + + ", yfii=" + yfii + ", fil=" + fil + ", aave=" + aave + ", bnt=" + bnt + ", grt=" + grt + ", snx=" + + snx + ", storj=" + storj + ", sushi=" + sushi + ", matic=" + matic + ", skl=" + skl + ", ada=" + ada + + ", ankr=" + ankr + ", crv=" + crv + ", icp=" + icp + ", nkn=" + nkn + ", ogn=" + ogn + ", inch1=" + + inch1 + ", usdt=" + usdt + ", forth=" + forth + ", ctsi=" + ctsi + ", trb=" + trb + ", poly=" + poly + + ", mir=" + mir + ", rlc=" + rlc + ", dot=" + dot + ", sol=" + sol + ", doge=" + doge + ", mln=" + mln + + ", gtc=" + gtc + ", amp=" + amp + ", shib=" + shib + ", chz=" + chz + ", keep=" + keep + ", lpt=" + + lpt + ", qnt=" + qnt + ", bond=" + bond + ", rly=" + rly + ", clv=" + clv + ", farm=" + farm + + ", mask=" + mask + ", fet=" + fet + ", pax=" + pax + ", ach=" + ach + ", asm=" + asm + ", pla=" + pla + + ", rai=" + rai + ", tribe=" + tribe + ", orn=" + orn + ", iotx=" + iotx + ", ust=" + ust + ", quick=" + + quick + ", axs=" + axs + ", req=" + req + ", wluna=" + wluna + ", tru=" + tru + ", rad=" + rad + + ", coti=" + coti + ", ddx=" + ddx + ", suku=" + suku + ", rgt=" + rgt + ", xyo=" + xyo + ", zen=" + + zen + ", auction=" + auction + ", busd=" + busd + ", jasmy=" + jasmy + ", wcfg=" + wcfg + ", btrst=" + + btrst + ", agld=" + agld + ", avax=" + avax + ", fx=" + fx + ", trac=" + trac + ", lcx=" + lcx + + ", arpa=" + arpa + ", badger=" + badger + ", krl=" + krl + ", perp=" + perp + ", rari=" + rari + + ", deso=" + deso + ", api3=" + api3 + ", nct=" + nct + ", shping=" + shping + ", upi=" + upi + + ", cro=" + cro + ", avt=" + avt + ", mdt=" + mdt + ", vgx=" + vgx + ", alcx=" + alcx + ", coval=" + + coval + ", fox=" + fox + ", musd=" + musd + ", gala=" + gala + ", powr=" + powr + ", gyen=" + gyen + + ", alice=" + alice + ", inv=" + inv + ", lqty=" + lqty + ", pro=" + pro + ", spell=" + spell + + ", ens=" + ens + ", dia=" + dia + ", blz=" + blz + ", ctx=" + ctx + ", idex=" + idex + ", mco2=" + + mco2 + ", pols=" + pols + ", super1=" + super1 + ", unfi=" + unfi + ", stx=" + stx + ", gods=" + gods + + ", imx=" + imx + ", rbn=" + rbn + ", bico=" + bico + ", gfi=" + gfi + ", glm=" + glm + ", mpl=" + mpl + + ", plu=" + plu + ", fida=" + fida + ", orca=" + orca + ", crpt=" + crpt + ", qsp=" + qsp + ", rndr=" + + rndr + ", syn=" + syn + ", aioz=" + aioz + ", aergo=" + aergo + ", high=" + high + ", rose=" + rose + + ", ape=" + ape + ", mina=" + mina + ", gmt=" + gmt + ", gst=" + gst + ", gal=" + gal + ", dnt=" + dnt + + ", flow=" + flow + ", sand=" + sand + ", op=" + op + ", ksm=" + ksm + "]"; + } + +} diff --git a/src/main/java/ch/xxx/trader/dtos/QuoteCbSmall.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteCbSmall.java similarity index 97% rename from src/main/java/ch/xxx/trader/dtos/QuoteCbSmall.java rename to backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteCbSmall.java index 2b5062e5..c526049e 100644 --- a/src/main/java/ch/xxx/trader/dtos/QuoteCbSmall.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteCbSmall.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.entity; import java.math.BigDecimal; import java.util.Date; diff --git a/src/main/java/ch/xxx/trader/dtos/QuoteIb.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteIb.java similarity index 77% rename from src/main/java/ch/xxx/trader/dtos/QuoteIb.java rename to backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteIb.java index 80fea249..acbef22f 100644 --- a/src/main/java/ch/xxx/trader/dtos/QuoteIb.java +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/QuoteIb.java @@ -13,13 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ch.xxx.trader.dtos; +package ch.xxx.trader.domain.model.entity; import java.math.BigDecimal; import java.util.Date; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import com.fasterxml.jackson.annotation.JsonProperty; @@ -29,8 +33,11 @@ public class QuoteIb implements Quote { @Id private ObjectId _id; + @NotNull + @Indexed(name = "QuoteIb-createdAt") @JsonProperty - private Date createdAt = new Date(); + private Date createdAt = new Date(); + @NotBlank private final String pair; private final BigDecimal bid; private final BigDecimal bidAmt; @@ -43,6 +50,8 @@ public class QuoteIb implements Quote { private final BigDecimal high24h; private final BigDecimal low24h; private final BigDecimal openToday; + private final BigDecimal highToday; + private final BigDecimal lowToday; private final BigDecimal vwapToday; private final BigDecimal vwap24h; private final Date serverTimeUTC; @@ -50,8 +59,8 @@ public class QuoteIb implements Quote { public QuoteIb(@JsonProperty("pair") String pair,@JsonProperty("bid") BigDecimal bid, @JsonProperty("bidAmt") BigDecimal bidAmt,@JsonProperty("ask") BigDecimal ask,@JsonProperty("askAmt") BigDecimal askAmt, - @JsonProperty("lastPrice") BigDecimal lastPrice,@JsonProperty("stAmt") BigDecimal stAmt, @JsonProperty("volume24h") BigDecimal volume24h,@JsonProperty("volumeToday") BigDecimal volumeToday,@JsonProperty("high24h") BigDecimal high24h, - @JsonProperty("low24h") BigDecimal low24h,@JsonProperty("openToday") BigDecimal openToday,@JsonProperty("vwapToday") BigDecimal vwapToday,@JsonProperty("vwap24h") BigDecimal vwap24h,@JsonProperty("serverTimeUTC") Date serverTimeUTC) { + @JsonProperty("lastPrice") BigDecimal lastPrice,@JsonProperty("lastAmt") BigDecimal stAmt, @JsonProperty("volume24h") BigDecimal volume24h,@JsonProperty("volumeToday") BigDecimal volumeToday,@JsonProperty("high24h") BigDecimal high24h, + @JsonProperty("low24h") BigDecimal low24h, @JsonProperty("openToday") BigDecimal openToday, @JsonProperty("highToday") BigDecimal highToday, @JsonProperty("lowToday") BigDecimal lowToday, @JsonProperty("vwapToday") BigDecimal vwapToday,@JsonProperty("vwap24h") BigDecimal vwap24h,@JsonProperty("serverTimeUTC") Date serverTimeUTC) { super(); this.pair = pair; this.bid = bid; @@ -65,10 +74,18 @@ public QuoteIb(@JsonProperty("pair") String pair,@JsonProperty("bid") BigDecima this.high24h = high24h; this.low24h = low24h; this.openToday = openToday; + this.highToday = highToday; + this.lowToday = lowToday; this.vwapToday = vwapToday; this.vwap24h = vwap24h; this.serverTimeUTC = serverTimeUTC; } + public BigDecimal getHighToday() { + return highToday; + } + public BigDecimal getLowToday() { + return lowToday; + } public String getPair() { return pair; } diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/entity/RevokedToken.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/RevokedToken.java new file mode 100644 index 00000000..b85b558a --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/RevokedToken.java @@ -0,0 +1,102 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.entity; + +import java.time.LocalDateTime; +import java.util.Objects; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@Document +public class RevokedToken { + @Id + private ObjectId _id; + @NotBlank + @Size(min = 2) + @JsonProperty + private String name; + @JsonProperty + private String uuid; + @NotNull + @JsonProperty + private LocalDateTime lastLogout; + + public RevokedToken() {} + + public RevokedToken(ObjectId _id, String name, String uuid, LocalDateTime lastLogout) { + super(); + this._id = _id; + this.name = name; + this.uuid = uuid; + this.lastLogout = lastLogout; + } + + public ObjectId get_id() { + return _id; + } + public void set_id(ObjectId _id) { + this._id = _id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getUuid() { + return uuid; + } + public void setUuid(String uuid) { + this.uuid = uuid; + } + public LocalDateTime getLastLogout() { + return lastLogout; + } + public void setLastLogout(LocalDateTime lastLogout) { + this.lastLogout = lastLogout; + } + + @Override + public int hashCode() { + return Objects.hash(_id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RevokedToken other = (RevokedToken) obj; + return Objects.equals(_id, other._id); + } + + @Override + public String toString() { + return "RevokedToken [_id=" + _id + ", name=" + name + ", uuid=" + uuid + ", lastLogout=" + lastLogout + "]"; + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosDay.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosDay.java new file mode 100644 index 00000000..df2e3171 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosDay.java @@ -0,0 +1,67 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.entity.paxos; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class PaxosDay { + private String high; + private String low; + private String open; + private String volume; + @JsonProperty("volume_weighted_average_price") + private String volumeWeightedAveragePrice; + @JsonProperty("last_execution") + private String lastExecution; + private PaxosTimeRange range; + + public String getHigh() { + return high; + } + public void setHigh(String high) { + this.high = high; + } + public String getLow() { + return low; + } + public void setLow(String low) { + this.low = low; + } + public String getOpen() { + return open; + } + public void setOpen(String open) { + this.open = open; + } + public String getVolume() { + return volume; + } + public void setVolume(String volume) { + this.volume = volume; + } + public String getVolumeWeightedAveragePrice() { + return volumeWeightedAveragePrice; + } + public void setVolumeWeightedAveragePrice(String volumeWeightedAveragePrice) { + this.volumeWeightedAveragePrice = volumeWeightedAveragePrice; + } + public PaxosTimeRange getRange() { + return range; + } + public void setRange(PaxosTimeRange range) { + this.range = range; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosPrice.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosPrice.java new file mode 100644 index 00000000..6b48cd7c --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosPrice.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.entity.paxos; + +public class PaxosPrice { + private String price; + private String amount; + + public String getPrice() { + return price; + } + public void setPrice(String price) { + this.price = price; + } + public String getAmount() { + return amount; + } + public void setAmount(String amount) { + this.amount = amount; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosQuote.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosQuote.java new file mode 100644 index 00000000..d577007f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosQuote.java @@ -0,0 +1,78 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.entity.paxos; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class PaxosQuote { + private String market; + @JsonProperty("best_bid") + private PaxosPrice bestBid; + @JsonProperty("best_ask") + private PaxosPrice bestAsk; + @JsonProperty("last_execution") + private PaxosPrice lastExecution; + private PaxosDay today; + @JsonProperty("last_day") + private PaxosDay lastDay; + @JsonProperty("snapshot_at") + private Date snapshotAt; + + public String getMarket() { + return market; + } + public void setMarket(String market) { + this.market = market; + } + public PaxosPrice getBestBid() { + return bestBid; + } + public void setBestBid(PaxosPrice bestBid) { + this.bestBid = bestBid; + } + public PaxosPrice getBestAsk() { + return bestAsk; + } + public void setBestAsk(PaxosPrice bestAsk) { + this.bestAsk = bestAsk; + } + public PaxosPrice getLastExecution() { + return lastExecution; + } + public void setLastExecution(PaxosPrice lastExecution) { + this.lastExecution = lastExecution; + } + public PaxosDay getToday() { + return today; + } + public void setToday(PaxosDay today) { + this.today = today; + } + public PaxosDay getLastDay() { + return lastDay; + } + public void setLastDay(PaxosDay lastDay) { + this.lastDay = lastDay; + } + public Date getSnapshotAt() { + return snapshotAt; + } + public void setSnapshotAt(Date snapshotAt) { + this.snapshotAt = snapshotAt; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosTimeRange.java b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosTimeRange.java new file mode 100644 index 00000000..74fe42c9 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/model/entity/paxos/PaxosTimeRange.java @@ -0,0 +1,36 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.model.entity.paxos; + +import java.util.Date; + +public class PaxosTimeRange { + private Date begin; + private Date end; + + public Date getBegin() { + return begin; + } + public void setBegin(Date begin) { + this.begin = begin; + } + public Date getEnd() { + return end; + } + public void setEnd(Date end) { + this.end = end; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/services/MyEventProducer.java b/backend/src/main/java/ch/xxx/trader/domain/services/MyEventProducer.java new file mode 100644 index 00000000..8a951064 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/services/MyEventProducer.java @@ -0,0 +1,10 @@ +package ch.xxx.trader.domain.services; + +import ch.xxx.trader.domain.model.entity.MyUser; +import ch.xxx.trader.domain.model.entity.RevokedToken; +import reactor.core.publisher.Mono; + +public interface MyEventProducer { + Mono sendNewUser(MyUser dto); + Mono sendUserLogout(RevokedToken dto); +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/services/MyOrderBookClient.java b/backend/src/main/java/ch/xxx/trader/domain/services/MyOrderBookClient.java new file mode 100644 index 00000000..097c5e0a --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/services/MyOrderBookClient.java @@ -0,0 +1,24 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.services; + +import reactor.core.publisher.Mono; + +public interface MyOrderBookClient { + Mono getOrderbookBitfinex(String currpair); + Mono getOrderbookBitstamp(String currpair); + Mono getOrderbookItbit(String currpair); +} diff --git a/backend/src/main/java/ch/xxx/trader/domain/services/MyUserService.java b/backend/src/main/java/ch/xxx/trader/domain/services/MyUserService.java new file mode 100644 index 00000000..083e9e73 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/domain/services/MyUserService.java @@ -0,0 +1,34 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.domain.services; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Map; + +import ch.xxx.trader.domain.model.dto.AuthCheck; +import ch.xxx.trader.domain.model.dto.RefreshTokenDto; +import ch.xxx.trader.domain.model.entity.MyUser; +import reactor.core.publisher.Mono; + +public interface MyUserService { + void updateLoggedOutUsers(); + Mono postAuthorize(AuthCheck authcheck, Map header); + Mono postUserSignin(MyUser myUser); + Mono postLogout(String bearerStr); + Mono postUserLogin(MyUser myUser) throws NoSuchAlgorithmException, InvalidKeySpecException; + Mono refreshToken(String bearerStr); +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/common/DtoUtils.java b/backend/src/main/java/ch/xxx/trader/usecase/common/DtoUtils.java new file mode 100644 index 00000000..b055e0bb --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/common/DtoUtils.java @@ -0,0 +1,72 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.common; + +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +public class DtoUtils { + public static final String CREATEDAT = "createdAt"; + + public static ObjectMapper produceObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return objectMapper; + } + + // source: https://dzone.com/articles/setters-method-handles-and-java-11 + @SuppressWarnings("rawtypes") + public static Function createGetter(final MethodHandles.Lookup lookup, final MethodHandle getter) throws Exception { + final CallSite site = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), // signature of method Function.apply after type + // erasure + getter, getter.type()); // actual signature of getter + try { + return (Function) site.getTarget().invokeExact(); + } catch (final Exception e) { + throw e; + } catch (final Throwable e) { + throw new Error(e); + } + } + + // source: https://dzone.com/articles/setters-method-handles-and-java-11 + @SuppressWarnings("rawtypes") + public static BiConsumer createSetter(final MethodHandles.Lookup lookup, final MethodHandle setter) + throws Exception { + final CallSite site = LambdaMetafactory.metafactory(lookup, "accept", MethodType.methodType(BiConsumer.class), + MethodType.methodType(void.class, Object.class, Object.class), // signature of method BiConsumer.accept + // after type erasure + setter, setter.type()); // actual signature of setter + try { + return (BiConsumer) site.getTarget().invokeExact(); + } catch (final Exception e) { + throw e; + } catch (final Throwable e) { + throw new Error(e); + } + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/common/LastlogoutTimestampExtractor.java b/backend/src/main/java/ch/xxx/trader/usecase/common/LastlogoutTimestampExtractor.java new file mode 100644 index 00000000..d835377a --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/common/LastlogoutTimestampExtractor.java @@ -0,0 +1,34 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.common; + +import java.sql.Timestamp; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.streams.processor.TimestampExtractor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.xxx.trader.domain.model.entity.RevokedToken; + +public class LastlogoutTimestampExtractor implements TimestampExtractor { + private static final Logger LOGGER = LoggerFactory.getLogger(LastlogoutTimestampExtractor.class); + + @Override + public long extract(ConsumerRecord record, long partitionTime) { + RevokedToken revokedToken = DtoUtils.produceObjectMapper().convertValue(record.value(), RevokedToken.class); +// LOGGER.info(revokedToken.toString()); + return Timestamp.valueOf(revokedToken.getLastLogout()).getTime(); + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/mappers/EventMapper.java b/backend/src/main/java/ch/xxx/trader/usecase/mappers/EventMapper.java new file mode 100644 index 00000000..f0a61838 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/mappers/EventMapper.java @@ -0,0 +1,56 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.mappers; + +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Service +public class EventMapper { + private static final Logger LOG = LoggerFactory.getLogger(EventMapper.class); + private final ObjectMapper objectMapper; + + public EventMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public String mapDtoToString(Object dto) { + String dtoJson; + try { + dtoJson = this.objectMapper.writeValueAsString(dto); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return dtoJson; + } + + public Optional mapJsonToObject(String jsonString, Class myClass) { + Optional resultOpt; + try { + resultOpt = Optional.ofNullable(this.objectMapper.readValue(jsonString, myClass)); + } catch (Exception e) { + LOG.warn(String.format("Failed to deserialize %s", jsonString), e); + resultOpt = Optional.empty(); + } + return resultOpt; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/mappers/ReportMapper.java b/backend/src/main/java/ch/xxx/trader/usecase/mappers/ReportMapper.java new file mode 100644 index 00000000..33887d17 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/mappers/ReportMapper.java @@ -0,0 +1,44 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.mappers; + +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.model.dto.QuotePdf; +import ch.xxx.trader.domain.model.entity.QuoteBf; +import ch.xxx.trader.domain.model.entity.QuoteBs; +import ch.xxx.trader.domain.model.entity.QuoteIb; + +@Service +public class ReportMapper { + + public QuotePdf convert(QuoteIb quote) { + QuotePdf quotePdf = new QuotePdf(quote.getLastPrice(), quote.getPair(), quote.getVolume24h(), + quote.getCreatedAt(), quote.getBid(), quote.getAsk()); + return quotePdf; + } + + public QuotePdf convert(QuoteBs quote) { + QuotePdf quotePdf = new QuotePdf(quote.getLast(), quote.getPair(), quote.getVolume(), quote.getCreatedAt(), + quote.getBid(), quote.getAsk()); + return quotePdf; + } + + public QuotePdf convert(QuoteBf quote) { + QuotePdf quotePdf = new QuotePdf(quote.getLast_price(), quote.getPair(), quote.getVolume(), quote.getCreatedAt(), quote.getBid(), quote.getAsk()); + return quotePdf; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/BitfinexService.java b/backend/src/main/java/ch/xxx/trader/usecase/services/BitfinexService.java new file mode 100644 index 00000000..48330b39 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/BitfinexService.java @@ -0,0 +1,264 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.MongoUtils; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.QuoteBf; +import ch.xxx.trader.domain.services.MyOrderBookClient; +import ch.xxx.trader.usecase.common.DtoUtils; +import ch.xxx.trader.usecase.mappers.ReportMapper; +import ch.xxx.trader.usecase.services.ServiceUtils.MyTimeFrame; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +@Service +public class BitfinexService { + private static final Logger LOG = LoggerFactory.getLogger(BitfinexService.class); + public static final String BF_HOUR_COL = "quoteBfHour"; + public static final String BF_DAY_COL = "quoteBfDay"; + public static volatile boolean singleInstanceLock = false; + private final MyOrderBookClient orderBookClient; + private final ReportMapper reportMapper; + private final MyMongoRepository myMongoRepository; + private final ServiceUtils serviceUtils; + private final Scheduler mongoScheduler = Schedulers.newBoundedElastic(5, 10, "mongoImport", 10); + @Value("${single.instance.deployment:false}") + private boolean singleInstanceDeployment; + + public BitfinexService(ServiceUtils serviceUtils, MyOrderBookClient orderBookClient, ReportMapper reportMapper, + MyMongoRepository myMongoRepository) { + this.orderBookClient = orderBookClient; + this.reportMapper = reportMapper; + this.myMongoRepository = myMongoRepository; + this.serviceUtils = serviceUtils; + } + + public Mono getOrderbook(String currpair) { + return this.orderBookClient.getOrderbookBitfinex(currpair); + } + + public Mono insertQuote(Mono quote) { + return this.myMongoRepository.insert(quote); + } + + public Mono currentQuote(String pair) { + Query query = MongoUtils.buildCurrentQuery(Optional.of(pair)); + return this.myMongoRepository.findOne(query, QuoteBf.class); + } + + public Flux tfQuotes(String timeFrame, String pair) { + return this.serviceUtils.tfQuotes(timeFrame, pair, QuoteBf.class, BF_HOUR_COL, BF_DAY_COL); + } + + public Mono pdfReport(String timeFrame, String pair) { + return this.serviceUtils.pdfReport(timeFrame, pair, QuoteBf.class, BF_HOUR_COL, BF_DAY_COL, + this.reportMapper::convert); + } + + public Mono createBfAvg() { + Mono result = Mono.empty(); + if ((this.singleInstanceDeployment && !BitfinexService.singleInstanceLock) || !this.singleInstanceDeployment) { + BitfinexService.singleInstanceLock = true; + result = this.myMongoRepository.ensureIndex(BF_HOUR_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + BF_HOUR_COL + ") failed.", ex)) +// .doOnError(ex -> LOG.info("ensureIndex(" + BF_HOUR_COL + ") failed.", ex)) + .then(this.myMongoRepository.ensureIndex(BF_DAY_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + BF_DAY_COL + ") failed.", ex)) +// .doOnError(ex -> LOG.info("ensureIndex(" + BF_DAY_COL + ") failed.", ex))) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + BF_DAY_COL + ") failed.", ex))) + .map(value -> this.createHourDayAvg()).timeout(Duration.ofHours(2L)) +// .doOnError(ex -> LOG.info("createBfAvg() failed.", ex)) + .onErrorContinue((ex, val) -> LOG.info("createBfAvg() failed.", ex)) + .subscribeOn(this.mongoScheduler); + } + return result; + } + + private String createHourDayAvg() { + LOG.info("createHourDayAvg()"); + CompletableFuture future3 = CompletableFuture.supplyAsync(() -> { + this.createBfHourlyAvg(); + return "createBfHourlyAvg() Done."; + }, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS)); + CompletableFuture future4 = CompletableFuture.supplyAsync(() -> { + this.createBfDailyAvg(); + return "createBfDailyAvg() Done."; + }, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS)); + String combined = Stream.of(future3, future4).map(CompletableFuture::join).collect(Collectors.joining(" ")); + LOG.info(combined); + return "done"; + } + + private void createBfHourlyAvg() { + LocalDateTime startAll = LocalDateTime.now(); + MyTimeFrame timeFrame = this.serviceUtils.createTimeFrame(BF_HOUR_COL, QuoteBf.class, true); + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + Calendar now = Calendar.getInstance(); + now.setTime(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + while (timeFrame.end().before(now)) { + Date start = new Date(); + Query query = new Query(); + query.addCriteria( + Criteria.where(DtoUtils.CREATEDAT).gt(timeFrame.begin().getTime()).lt(timeFrame.end().getTime())); + // Bitfinex + Mono> collectBf = this.myMongoRepository.find(query, QuoteBf.class) + .timeout(Duration.ofSeconds(5L)).doOnError(ex -> LOG.warn("Bitfinex prepare hour data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(mongoScheduler) + .collectMultimap(quote -> quote.getPair(), quote -> quote) + .map(multimap -> multimap.keySet().stream() + .map(key -> makeBfQuoteHour(key, multimap, timeFrame.begin(), timeFrame.end())) + .collect(Collectors.toList())) + .flatMap(myList -> Mono + .just(myList.stream().flatMap(Collection::stream).collect(Collectors.toList()))); + collectBf.filter(Predicate.not(Collection::isEmpty)) + .flatMap(myColl -> this.myMongoRepository.insertAll(Mono.just(myColl), BF_HOUR_COL) + .timeout(Duration.ofSeconds(5L)) + .doOnError(ex -> LOG.warn("Bitfinex prepare hour data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(mongoScheduler).collectList()) + .block(); + + timeFrame.begin().add(Calendar.DAY_OF_YEAR, 1); + timeFrame.end().add(Calendar.DAY_OF_YEAR, 1); + LOG.info("Prepared Bitfinex Hour Data for: " + sdf.format(timeFrame.begin().getTime()) + " Time: " + + (new Date().getTime() - start.getTime()) + "ms"); + } + LOG.info(this.serviceUtils.createAvgLogStatement(startAll, "Prepared Bitfinex Hourly Data Time:")); + } + + private void createBfDailyAvg() { + LocalDateTime startAll = LocalDateTime.now(); + MyTimeFrame timeFrame = this.serviceUtils.createTimeFrame(BF_DAY_COL, QuoteBf.class, false); + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + Calendar now = Calendar.getInstance(); + now.setTime(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + while (timeFrame.end().before(now)) { + Date start = new Date(); + Query query = new Query(); + query.addCriteria( + Criteria.where(DtoUtils.CREATEDAT).gt(timeFrame.begin().getTime()).lt(timeFrame.end().getTime())); + // Bitfinex + Mono> collectBf = this.myMongoRepository.find(query, QuoteBf.class) + .timeout(Duration.ofSeconds(5L)).doOnError(ex -> LOG.warn("Bitfinex prepare day data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler) + .collectMultimap(quote -> quote.getPair(), quote -> quote) + .map(multimap -> multimap.keySet().stream() + .map(key -> makeBfQuoteDay(key, multimap, timeFrame.begin(), timeFrame.end())) + .collect(Collectors.toList())) + .flatMap(myList -> Mono + .just(myList.stream().flatMap(Collection::stream).collect(Collectors.toList()))); + collectBf.filter(Predicate.not(Collection::isEmpty)) + .flatMap(myColl -> this.myMongoRepository.insertAll(Mono.just(myColl), BF_DAY_COL) + .subscribeOn(mongoScheduler).timeout(Duration.ofSeconds(5L)) + .doOnError(ex -> LOG.warn("Bitfinex prepare day data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler).collectList()) + .subscribeOn(this.mongoScheduler).block(); + + timeFrame.begin().add(Calendar.DAY_OF_YEAR, 1); + timeFrame.end().add(Calendar.DAY_OF_YEAR, 1); + LOG.info("Prepared Bitfinex Day Data for: " + sdf.format(timeFrame.begin().getTime()) + " Time: " + + (new Date().getTime() - start.getTime()) + "ms"); + } + LOG.info(this.serviceUtils.createAvgLogStatement(startAll, "Prepared Bitfinex Daily Data Time:")); + } + + private Collection makeBfQuoteHour(String key, Map> multimap, Calendar begin, + Calendar end) { + List hours = this.serviceUtils.createDayHours(begin); + List hourQuotes = new LinkedList(); + for (int i = 0; i < 24; i++) { + QuoteBf quoteBf = new QuoteBf(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, ""); + quoteBf.setCreatedAt(hours.get(i).getTime()); + final int x = i; + long count = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).count(); + if (count > 2) { + QuoteBf hourQuote = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).reduce(quoteBf, (q1, q2) -> avgBfQuote(q1, q2, count)); + hourQuote.setPair(key); + hourQuotes.add(hourQuote); + } + } + return hourQuotes; + } + + private Collection makeBfQuoteDay(String key, Map> multimap, Calendar begin, + Calendar end) { + List hourQuotes = new LinkedList(); + + QuoteBf quoteBf = new QuoteBf(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, ""); + quoteBf.setCreatedAt(begin.getTime()); + long count = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).count(); + if (count > 2) { + QuoteBf hourQuote = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).reduce(quoteBf, (q1, q2) -> avgBfQuote(q1, q2, count)); + hourQuote.setPair(key); + hourQuotes.add(hourQuote); + } + return hourQuotes; + } + + private QuoteBf avgBfQuote(QuoteBf q1, QuoteBf q2, long count) { + QuoteBf myQuote = new QuoteBf(this.serviceUtils.avgHourValue(q1.getMid(), q2.getMid(), count), + this.serviceUtils.avgHourValue(q1.getBid(), q2.getBid(), count), + this.serviceUtils.avgHourValue(q1.getAsk(), q2.getAsk(), count), + this.serviceUtils.avgHourValue(q1.getLast_price(), q2.getLast_price(), count), + this.serviceUtils.avgHourValue(q1.getLow(), q2.getLow(), count), + this.serviceUtils.avgHourValue(q1.getHigh(), q2.getHigh(), count), + this.serviceUtils.avgHourValue(q1.getVolume(), q2.getVolume(), count), q1.getTimestamp()); + myQuote.setCreatedAt(q1.getCreatedAt()); + return myQuote; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/BitstampService.java b/backend/src/main/java/ch/xxx/trader/usecase/services/BitstampService.java new file mode 100644 index 00000000..5701378d --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/BitstampService.java @@ -0,0 +1,263 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.MongoUtils; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.QuoteBs; +import ch.xxx.trader.domain.services.MyOrderBookClient; +import ch.xxx.trader.usecase.common.DtoUtils; +import ch.xxx.trader.usecase.mappers.ReportMapper; +import ch.xxx.trader.usecase.services.ServiceUtils.MyTimeFrame; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +@Service +public class BitstampService { + private static final Logger LOG = LoggerFactory.getLogger(BitstampService.class); + public static final String BS_HOUR_COL = "quoteBsHour"; + public static final String BS_DAY_COL = "quoteBsDay"; + public static volatile boolean singleInstanceLock = false; + private final MyOrderBookClient orderBookClient; + private final ReportMapper reportMapper; + private final MyMongoRepository myMongoRepository; + private final ServiceUtils serviceUtils; + private final Scheduler mongoScheduler = Schedulers.newBoundedElastic(5, 10, "mongoImport", 10); + @Value("${single.instance.deployment:false}") + private boolean singleInstanceDeployment; + + public BitstampService(MyOrderBookClient orderBookClient, MyMongoRepository myMongoRepository, + ServiceUtils serviceUtils, ReportMapper reportMapper) { + this.orderBookClient = orderBookClient; + this.reportMapper = reportMapper; + this.myMongoRepository = myMongoRepository; + this.serviceUtils = serviceUtils; + } + + public Mono insertQuote(Mono quote) { + return this.myMongoRepository.insert(quote); + } + + public Mono getOrderbook(String currpair) { + return this.orderBookClient.getOrderbookBitstamp(currpair); + } + + public Mono currentQuoteBtc(String pair) { + Query query = MongoUtils.buildCurrentQuery(Optional.of(pair)); + return this.myMongoRepository.findOne(query, QuoteBs.class); + } + + public Flux tfQuotesBtc(String timeFrame, String pair) { + return this.serviceUtils.tfQuotes(timeFrame, pair, QuoteBs.class, BS_HOUR_COL, BS_DAY_COL); + } + + public Mono pdfReport(String timeFrame, String pair) { + return this.serviceUtils.pdfReport(timeFrame, pair, QuoteBs.class, BS_HOUR_COL, BS_DAY_COL, this.reportMapper::convert); + } + + public Mono createBsAvg() { + Mono result = Mono.empty(); + if ((this.singleInstanceDeployment && !BitstampService.singleInstanceLock) || !this.singleInstanceDeployment) { + BitstampService.singleInstanceLock = true; + result = this.myMongoRepository.ensureIndex(BS_HOUR_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) +// .doOnError(ex -> LOG.info("ensureIndex(" + BS_HOUR_COL + ") failed.", ex)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + BS_HOUR_COL + ") failed.", ex)) + .then(this.myMongoRepository.ensureIndex(BS_DAY_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) +// .doOnError(ex -> LOG.info("ensureIndex(" + BS_DAY_COL + ") failed.", ex)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + BS_DAY_COL + ") failed.", ex))) + .map(value -> this.createHourDayAvg()).timeout(Duration.ofHours(3L)) + .onErrorContinue((ex, val) -> LOG.info("createBsAvg() failed.", ex)) +// .doOnError(ex -> LOG.info("createBsAvg() failed.", ex)) + .subscribeOn(this.mongoScheduler); + } + return result; + } + + private String createHourDayAvg() { + LOG.info("createHourDayAvg()"); + CompletableFuture future1 = CompletableFuture.supplyAsync(() -> { + this.createBsHourlyAvg(); + return "createBsHourlyAvg() Done."; + }, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS)); + CompletableFuture future2 = CompletableFuture.supplyAsync(() -> { + this.createBsDailyAvg(); + return "createBsDailyAvg() Done."; + }, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS)); + String combined = Stream.of(future1, future2).map(CompletableFuture::join).collect(Collectors.joining(" ")); + LOG.info(combined); + return "done"; + } + + private void createBsHourlyAvg() { + LocalDateTime startAll = LocalDateTime.now(); + MyTimeFrame timeFrame = this.serviceUtils.createTimeFrame(BS_HOUR_COL, QuoteBs.class, true); + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + Calendar now = Calendar.getInstance(); + now.setTime(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + while (timeFrame.end().before(now)) { + Date start = new Date(); + Query query = new Query(); + query.addCriteria( + Criteria.where(DtoUtils.CREATEDAT).gt(timeFrame.begin().getTime()).lt(timeFrame.end().getTime())); + // Bitstamp + Mono> collectBs = this.myMongoRepository.find(query, QuoteBs.class) + .timeout(Duration.ofSeconds(5L)).doOnError(ex -> LOG.warn("Bitstamp prepare hour data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler) + .collectMultimap(quote -> quote.getPair(), quote -> quote) + .map(multimap -> multimap.keySet().stream() + .map(key -> makeBsQuoteHour(key, multimap, timeFrame.begin(), timeFrame.end())) + .collect(Collectors.toList())) + .flatMap(myList -> Mono + .just(myList.stream().flatMap(Collection::stream).collect(Collectors.toList()))); + collectBs.filter(Predicate.not(Collection::isEmpty)) + .flatMap(myColl -> this.myMongoRepository.insertAll(Mono.just(myColl), BS_HOUR_COL) + .timeout(Duration.ofSeconds(5L)) + .doOnError(ex -> LOG.warn("Bitstamp prepare hour data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler).collectList()) + .subscribeOn(this.mongoScheduler).block(); + + timeFrame.begin().add(Calendar.DAY_OF_YEAR, 1); + timeFrame.end().add(Calendar.DAY_OF_YEAR, 1); + LOG.info("Prepared Bitstamp Hour Data for: " + sdf.format(timeFrame.begin().getTime()) + " Time: " + + (new Date().getTime() - start.getTime()) + "ms"); + } + LOG.info(this.serviceUtils.createAvgLogStatement(startAll, "Prepared Bitstamp Hourly Data Time:")); + } + + private void createBsDailyAvg() { + LocalDateTime startAll = LocalDateTime.now(); + MyTimeFrame timeFrame = this.serviceUtils.createTimeFrame(BS_DAY_COL, QuoteBs.class, false); + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + Calendar now = Calendar.getInstance(); + now.setTime(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + while (timeFrame.end().before(now)) { + Date start = new Date(); + Query query = new Query(); + query.addCriteria( + Criteria.where(DtoUtils.CREATEDAT).gt(timeFrame.begin().getTime()).lt(timeFrame.end().getTime())); + // Bitstamp + Mono> collectBs = this.myMongoRepository.find(query, QuoteBs.class) + .timeout(Duration.ofSeconds(5L)).doOnError(ex -> LOG.warn("Bitstamp prepare day data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler) + .collectMultimap(quote -> quote.getPair(), quote -> quote) + .map(multimap -> multimap.keySet().stream() + .map(key -> makeBsQuoteDay(key, multimap, timeFrame.begin(), timeFrame.end())) + .collect(Collectors.toList())) + .flatMap(myList -> Mono + .just(myList.stream().flatMap(Collection::stream).collect(Collectors.toList()))); + collectBs.filter(Predicate.not(Collection::isEmpty)) + .flatMap(myColl -> this.myMongoRepository.insertAll(Mono.just(myColl), BS_DAY_COL) + .timeout(Duration.ofSeconds(5L)) + .doOnError(ex -> LOG.warn("Bitstamp prepare hour data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler).collectList()) + .subscribeOn(this.mongoScheduler).block(); + + timeFrame.begin().add(Calendar.DAY_OF_YEAR, 1); + timeFrame.end().add(Calendar.DAY_OF_YEAR, 1); + LOG.info("Prepared Bitstamp Day Data for: " + sdf.format(timeFrame.begin().getTime()) + " Time: " + + (new Date().getTime() - start.getTime()) + "ms"); + } + LOG.info(this.serviceUtils.createAvgLogStatement(startAll, "Prepared Bitstamp Daily Data Time:")); + } + + private Collection makeBsQuoteDay(String key, Map> multimap, Calendar begin, + Calendar end) { + List hourQuotes = new LinkedList(); + + QuoteBs quoteBs = new QuoteBs(BigDecimal.ZERO, BigDecimal.ZERO, begin.getTime(), BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO); + quoteBs.setCreatedAt(begin.getTime()); + long count = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).count(); + if (count > 2) { + QuoteBs hourQuote = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).reduce(quoteBs, (q1, q2) -> avgBsQuote(q1, q2, count)); + hourQuote.setPair(key); + hourQuotes.add(hourQuote); + } + return hourQuotes; + } + + private Collection makeBsQuoteHour(String key, Map> multimap, Calendar begin, + Calendar end) { + List hours = this.serviceUtils.createDayHours(begin); + List hourQuotes = new LinkedList(); + for (int i = 0; i < 24; i++) { + QuoteBs quoteBs = new QuoteBs(BigDecimal.ZERO, BigDecimal.ZERO, hours.get(i).getTime(), BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO); + quoteBs.setCreatedAt(hours.get(i).getTime()); + final int x = i; + long count = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).count(); + if (count > 2) { + QuoteBs hourQuote = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).reduce(quoteBs, (q1, q2) -> avgBsQuote(q1, q2, count)); + hourQuote.setPair(key); + hourQuotes.add(hourQuote); + } + } + return hourQuotes; + } + + private QuoteBs avgBsQuote(QuoteBs q1, QuoteBs q2, long count) { + QuoteBs myQuote = new QuoteBs(this.serviceUtils.avgHourValue(q1.getHigh(), q2.getHigh(), count), + this.serviceUtils.avgHourValue(q1.getLast(), q2.getLast(), count), q1.getTimestamp(), + this.serviceUtils.avgHourValue(q1.getBid(), q2.getBid(), count), + this.serviceUtils.avgHourValue(q1.getVwap(), q2.getVwap(), count), + this.serviceUtils.avgHourValue(q1.getVolume(), q2.getVolume(), count), + this.serviceUtils.avgHourValue(q1.getLow(), q2.getLow(), count), + this.serviceUtils.avgHourValue(q1.getAsk(), q2.getAsk(), count), + this.serviceUtils.avgHourValue(q1.getOpen(), q2.getOpen(), count)); + myQuote.setCreatedAt(q1.getCreatedAt()); + return myQuote; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/CoinbaseService.java b/backend/src/main/java/ch/xxx/trader/usecase/services/CoinbaseService.java new file mode 100644 index 00000000..af42ac5f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/CoinbaseService.java @@ -0,0 +1,418 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.MongoUtils; +import ch.xxx.trader.domain.common.MongoUtils.TimeFrame; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.QuoteCb; +import ch.xxx.trader.domain.model.entity.QuoteCbSmall; +import ch.xxx.trader.usecase.common.DtoUtils; +import ch.xxx.trader.usecase.services.ServiceUtils.MyTimeFrame; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +@Service +public class CoinbaseService { + private static final Logger LOG = LoggerFactory.getLogger(CoinbaseService.class); + private static final Map cbFunctionCache = new ConcurrentHashMap<>(); + + private record GetSetMethodFunctions(Function getter, BiConsumer setter, + String propertyName, PropertyDescriptor propertyDescriptor) { + } + + public static final String CB_HOUR_COL = "quoteCbHour"; + public static final String CB_DAY_COL = "quoteCbDay"; + public static volatile boolean singleInstanceLock = false; + private final MyMongoRepository myMongoRepository; + private final ServiceUtils serviceUtils; + @Value("${kubernetes.pod.cpu.constraint}") + private boolean cpuConstraint; + private final List nonValueFieldNames = List.of("_id", "createdAt", "class"); + private final List propertyDescriptors; + private final Scheduler mongoScheduler = Schedulers.newBoundedElastic(6, 10, "mongoImport", 10); + @Value("${single.instance.deployment:false}") + private boolean singleInstanceDeployment; + @Value("${single.instance.slow-io:false}") + private boolean slowIo; + + public CoinbaseService(MyMongoRepository myMongoRepository, ServiceUtils serviceUtils) { + this.myMongoRepository = myMongoRepository; + this.serviceUtils = serviceUtils; + try { + BeanInfo beanInfo = Introspector.getBeanInfo(QuoteCb.class); + this.propertyDescriptors = Stream.of(beanInfo.getPropertyDescriptors()) + .filter(myDescriptor -> !this.nonValueFieldNames.contains(myDescriptor.getName())).toList(); + } catch (IntrospectionException e) { + throw new RuntimeException(e); + } + } + + public Mono insertQuote(Mono quote) { + return this.myMongoRepository.insert(quote); + } + + public Flux todayQuotesBc() { + Query query = MongoUtils.buildTodayQuery(Optional.empty()); + return this.myMongoRepository.find(query, QuoteCb.class).filter(CoinbaseService::filterEvenMinutes) + .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), + quote.getLtc())); + } + + public Flux sevenDaysQuotesBc() { + Query query = MongoUtils.build7DayQuery(Optional.empty()); + return this.myMongoRepository.find(query, QuoteCb.class, CB_HOUR_COL).filter(CoinbaseService::filterEvenMinutes) + .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), + quote.getLtc())); + } + + public Flux thirtyDaysQuotesBc() { + Query query = MongoUtils.build30DayQuery(Optional.empty()); + return this.myMongoRepository.find(query, QuoteCb.class, CB_DAY_COL).filter(CoinbaseService::filterEvenMinutes) + .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), + quote.getLtc())); + } + + public Flux nintyDaysQuotesBc() { + Query query = MongoUtils.build90DayQuery(Optional.empty()); + return this.myMongoRepository.find(query, QuoteCb.class, CB_DAY_COL).filter(CoinbaseService::filterEvenMinutes) + .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), + quote.getLtc())); + } + + public Flux sixMonthsQuotesBc() { + Query query = MongoUtils.buildTimeFrameQuery(Optional.empty(), TimeFrame.Month6); + return this.myMongoRepository.find(query, QuoteCb.class, CB_DAY_COL).filter(CoinbaseService::filterEvenMinutes) + .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), + quote.getLtc())); + } + + public Flux oneYearQuotesBc() { + Query query = MongoUtils.buildTimeFrameQuery(Optional.empty(), TimeFrame.Year1); + return this.myMongoRepository.find(query, QuoteCb.class, CB_DAY_COL).filter(CoinbaseService::filterEvenMinutes) + .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), + quote.getLtc())); + } + + public Mono currentQuoteBc() { + Query query = MongoUtils.buildCurrentQuery(Optional.empty()); + return this.myMongoRepository.findOne(query, QuoteCb.class); + } + + public Mono createCbAvg() { + Mono result = Mono.empty(); + if ((this.singleInstanceDeployment && !CoinbaseService.singleInstanceLock) || !this.singleInstanceDeployment) { + CoinbaseService.singleInstanceLock = true; + result = this.myMongoRepository.ensureIndex(CB_HOUR_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + CB_HOUR_COL + ") failed.", ex)) +// .doOnError(ex -> LOG.info("ensureIndex(" + CB_HOUR_COL + ") failed.", ex)) + .then(this.myMongoRepository.ensureIndex(CB_DAY_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) +// .doOnError(ex -> LOG.info("ensureIndex(" + CB_DAY_COL + ") failed.", ex)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + CB_DAY_COL + ") failed.", ex))) + .map(value -> this.createHourDayAvg()).timeout(Duration.ofHours(2L)) +// .doOnError(ex -> LOG.info("createCbAvg() failed.", ex)) + .onErrorContinue((ex, val) -> LOG.info("createCbAvg() failed.", ex)) + .subscribeOn(this.mongoScheduler); + } + return result; + } + + private String createHourDayAvg() { + LOG.info("createHourDayAvg()"); + LocalDateTime start = LocalDateTime.now(); + LOG.info("CpuConstraint property: " + this.cpuConstraint); + if (this.cpuConstraint) { + this.createCbIntervalAvg(false); + this.createCbIntervalAvg(true); + LOG.info(this.serviceUtils.createAvgLogStatement(start, "Prepared Coinbase Data Time:")); + } else { + // This can only be used on machines without cpu constraints. + CompletableFuture future7 = CompletableFuture.supplyAsync(() -> { + this.createCbIntervalAvg(false); + return "createCbHourlyAvg() Done."; + }, CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS)); + CompletableFuture future8 = CompletableFuture.supplyAsync(() -> { + this.createCbIntervalAvg(true); + return "createCbDailyAvg() Done."; + }, CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS)); + String combined = Stream.of(future7, future8).map(CompletableFuture::join).collect(Collectors.joining(" ")); + LOG.info(combined); + } + return "done."; + } + + private void processTimeFrame(MyTimeFrame timeFrame1, boolean isDay) { + Date start = new Date(); + final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + final var nonZeroProperties = new AtomicInteger(0); + Query query = new Query(); + query.addCriteria( + Criteria.where(DtoUtils.CREATEDAT).gt(timeFrame1.begin().getTime()).lt(timeFrame1.end().getTime())); + // Coinbase + final var logFailed = String.format("Coinbase prepare %s data failed", isDay ? "day" : "hour"); + Mono> collectCb = this.myMongoRepository.find(query, QuoteCb.class) + .timeout(this.slowIo ? Duration.ofSeconds(30L) : Duration.ofSeconds(10L)) + .doOnError(ex -> LOG.warn(logFailed, ex)).onErrorResume(ex -> { + LOG.warn(logFailed, ex); + return Mono.empty(); + }).subscribeOn(this.mongoScheduler).collectList() + .map(quotes -> this.createCbQuoteTimeFrame(timeFrame1, isDay, quotes)); + collectCb.filter(Predicate.not(Collection::isEmpty)) + .map(myColl -> this.countRelevantProperties(nonZeroProperties, myColl)) + .flatMap(myColl -> this.myMongoRepository.insertAll(Mono.just(myColl), isDay ? CB_DAY_COL : CB_HOUR_COL) + .timeout(this.slowIo ? Duration.ofSeconds(30L) : Duration.ofSeconds(10L)) + .doOnError(ex -> LOG.warn(logFailed, ex)).onErrorResume(ex -> { + LOG.warn(logFailed, ex); + return Mono.empty(); + }).subscribeOn(this.mongoScheduler).collectList()) + .subscribeOn(this.mongoScheduler).block(); + LOG.info(String.format("Prepared Coinbase %s Data for: ", isDay ? "Day" : "Hour") + + sdf.format(timeFrame1.begin().getTime()) + " Time: " + (new Date().getTime() - start.getTime()) + "ms" + + " 0 < properties: " + nonZeroProperties.get()); + } + + private Collection createCbQuoteTimeFrame(final MyTimeFrame timeFrame1, final boolean isDay, + List quotes) { + Date start = new Date(); + final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + var result = isDay ? this.makeCbQuoteDay(quotes, timeFrame1.begin(), timeFrame1.end()) + : this.makeCbQuoteHour(quotes, timeFrame1.begin(), timeFrame1.end()); + LOG.info(String.format("Calculate Coinbase %s Data for: ", isDay ? "Day" : "Hour") + + sdf.format(timeFrame1.begin().getTime()) + " Time: " + (new Date().getTime() - start.getTime()) + + "ms"); + return result; + } + + private void createCbIntervalAvg(boolean isDay) { + LOG.info(isDay ? "createCbDailyAvg()" : "createCbHourlyAvg()"); + LocalDateTime startAll = LocalDateTime.now(); + final MyTimeFrame timeFrame = this.serviceUtils.createTimeFrame(isDay ? CB_DAY_COL : CB_HOUR_COL, QuoteCb.class, + !isDay); + final Calendar now = Calendar.getInstance(); + now.setTime(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); + LOG.info("isDay: {}, TimeFrame.Begin: {}, TimeFrame.End: {}, now: {}", isDay, sdf.format(timeFrame.begin().getTime()), sdf.format(timeFrame.end().getTime()), sdf.format(now.getTime())); + this.createTimeFrames(timeFrame, now).stream() + .forEachOrdered(timeFrame1 -> this.processTimeFrame(timeFrame1, isDay)); + var logStmt = String.format("Prepared Coinbase %s Data Time:", isDay ? "Daily" : "Hourly"); + LOG.info(this.serviceUtils.createAvgLogStatement(startAll, logStmt)); + } + + private List createTimeFrames(final MyTimeFrame timeFrame, final Calendar now) { + final var timeFrames = new ArrayList(); + var begin = timeFrame.begin(); + var end = timeFrame.end(); + while (end.before(now)) { + var myTimeFrame = new MyTimeFrame(begin, end); + timeFrames.add(myTimeFrame); + begin = nextDay(begin); + end = nextDay(end); + } + return timeFrames; + } + + private Calendar nextDay(Calendar begin) { + var begin1 = GregorianCalendar.getInstance(); + begin1.setTime(begin.getTime()); + begin1.add(Calendar.DAY_OF_YEAR, 1); + begin = begin1; + return begin; + } + + private Collection countRelevantProperties(final AtomicInteger nonZeroProperties, + Collection myColl) { + var relevantProperties = myColl.stream().flatMap(myQuote -> Stream.of(this.propertiesNonZero(myQuote))) + .mapToInt(v -> v).max().orElse(0); + nonZeroProperties + .set(nonZeroProperties.get() < relevantProperties ? relevantProperties : nonZeroProperties.get()); + return myColl; + } + + private Collection makeCbQuoteDay(List quotes, Calendar begin, Calendar end) { + List hourQuotes = new LinkedList(); + QuoteCb quoteCb = new QuoteCb(); + quoteCb.setCreatedAt(begin.getTime()); + long count = quotes.stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).count(); + if (count > 2) { + quoteCb = quotes.stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).reduce(quoteCb, (q1, q2) -> avgCbQuotePeriod(q1, q2, count)); + hourQuotes.add(quoteCb); + } + return hourQuotes; + } + + private Collection makeCbQuoteHour(List quotes, Calendar begin, Calendar end) { + List hours = this.serviceUtils.createDayHours(begin); + List hourQuotes = new LinkedList(); + for (int i = 0; i < 24; i++) { + QuoteCb quoteCb = new QuoteCb(); + quoteCb.setCreatedAt(hours.get(i).getTime()); + final int x = i; + long count = quotes.stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).count(); + if (count > 2) { + quoteCb = quotes.stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).reduce(quoteCb, (q1, q2) -> avgCbQuotePeriod(q1, q2, count)); + hourQuotes.add(quoteCb); + } + } + return hourQuotes; + } + + private QuoteCb avgCbQuotePeriod(QuoteCb q1, QuoteCb q2, long count) { + QuoteCb result = avgCbQuotePeriodMF(q1, q2, count); + return result; + } + + private Integer propertiesNonZero(QuoteCb quote) { + var result = new AtomicInteger(0); + this.propertyDescriptors.forEach(myPropertyDescriptor -> { + try { + var gsmf = this.createGetMethodFunction(myPropertyDescriptor); + BigDecimal num1 = gsmf.getter.apply(quote); + result.set(num1.compareTo(BigDecimal.ZERO) > 0 ? result.addAndGet(1) : result.get()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + return result.get(); + } + + private QuoteCb avgCbQuotePeriodMF(QuoteCb q1, QuoteCb q2, long count) { + QuoteCb result = new QuoteCb(); + this.propertyDescriptors.forEach(myPropertyDescriptor -> { + try { + var gsmf = this.createGetMethodFunction(myPropertyDescriptor); + BigDecimal num1 = gsmf.getter.apply(q1); + BigDecimal num2 = gsmf.getter.apply(q2); + BigDecimal resultValue = this.serviceUtils.avgHourValue(num1, num2, count); + gsmf.setter.accept(result, resultValue); + result.setCreatedAt(q1.getCreatedAt()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + return result; + } + + private GetSetMethodFunctions createGetMethodFunction(PropertyDescriptor propertyDescriptor) throws Exception { + GetSetMethodFunctions gsmf = cbFunctionCache.get(propertyDescriptor.getName()); + // log.info(propertyDescriptor.getName()); + if (gsmf == null) { + synchronized (this) { + if (cbFunctionCache.size() > 10000) { + LOG.info("CbFunctionCache size: {}", cbFunctionCache.size()); + cbFunctionCache.clear(); + } + gsmf = cbFunctionCache.get(propertyDescriptor.getName()); + if (gsmf == null) { + final MethodHandles.Lookup lookupGetter = MethodHandles.lookup(); + final MethodHandles.Lookup lookupSetter = MethodHandles.lookup(); + record GetSetMethods(Method getterMethod, Method setterMethod) { + } + var result = switch (propertyDescriptor.getName().toLowerCase()) { + case "1inch" -> new GetSetMethods( + Stream.of(QuoteCb.class.getMethods()) + .filter(myMethod -> myMethod.getName().equalsIgnoreCase("get1Inch")).findFirst() + .orElseThrow(), + Stream.of(QuoteCb.class.getMethods()) + .filter(myMethod -> myMethod.getName().equalsIgnoreCase("set1Inch")).findFirst() + .orElseThrow()); + case "super" -> new GetSetMethods( + Stream.of(QuoteCb.class.getMethods()) + .filter(myMethod -> myMethod.getName().equalsIgnoreCase("getSuper")).findFirst() + .orElseThrow(), + Stream.of(QuoteCb.class.getMethods()) + .filter(myMethod -> myMethod.getName().equalsIgnoreCase("setSuper")).findFirst() + .orElseThrow()); + case "try" -> new GetSetMethods( + Stream.of(QuoteCb.class.getMethods()) + .filter(myMethod -> myMethod.getName().equalsIgnoreCase("getTry1")).findFirst() + .orElseThrow(), + Stream.of(QuoteCb.class.getMethods()) + .filter(myMethod -> myMethod.getName().equalsIgnoreCase("setTry1")).findFirst() + .orElseThrow()); + default -> + new GetSetMethods(propertyDescriptor.getReadMethod(), propertyDescriptor.getWriteMethod()); + }; + @SuppressWarnings("unchecked") + Function getterFunction = (Function) DtoUtils + .createGetter(lookupGetter, lookupGetter.unreflect(result.getterMethod())); + @SuppressWarnings("unchecked") + BiConsumer setterFunction = DtoUtils.createSetter(lookupSetter, + lookupSetter.unreflect(result.setterMethod())); + cbFunctionCache.put(propertyDescriptor.getName(), new GetSetMethodFunctions(getterFunction, + setterFunction, propertyDescriptor.getName(), propertyDescriptor)); + gsmf = cbFunctionCache.get(propertyDescriptor.getName()); + } + } + } + return gsmf; + } + + private static boolean filterEvenMinutes(QuoteCb quote) { + return MongoUtils.filterEvenMinutes(quote.getCreatedAt()); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/ItbitService.java b/backend/src/main/java/ch/xxx/trader/usecase/services/ItbitService.java new file mode 100644 index 00000000..5444f8f3 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/ItbitService.java @@ -0,0 +1,280 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.MongoUtils; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.QuoteIb; +import ch.xxx.trader.domain.services.MyOrderBookClient; +import ch.xxx.trader.usecase.common.DtoUtils; +import ch.xxx.trader.usecase.mappers.ReportMapper; +import ch.xxx.trader.usecase.services.ServiceUtils.MyTimeFrame; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +@Service +public class ItbitService { + private static final Logger LOG = LoggerFactory.getLogger(ItbitService.class); + public static final String IB_HOUR_COL = "quoteIbHour"; + public static final String IB_DAY_COL = "quoteIbDay"; + public static volatile boolean singleInstanceLock = false; + private final Map currpairs = new HashMap(); + private final MyOrderBookClient orderBookClient; + private final ReportMapper reportMapper; + private final MyMongoRepository myMongoRepository; + private final ServiceUtils serviceUtils; + private final Scheduler mongoScheduler = Schedulers.newBoundedElastic(5, 10, "mongoImport", 10); + @Value("${single.instance.deployment:false}") + private boolean singleInstanceDeployment; + + public ItbitService(MyOrderBookClient orderBookClient, ReportMapper reportMapper, + MyMongoRepository myMongoRepository, ServiceUtils serviceUtils) { + this.orderBookClient = orderBookClient; + this.reportMapper = reportMapper; + this.myMongoRepository = myMongoRepository; + this.serviceUtils = serviceUtils; + this.currpairs.put("btcusd", "XBTUSD"); + this.currpairs.put("btceur", "XBTEUR"); + } + + public Mono getOrderbook(String currpair) { + final String newCurrpair = currpair.equals("btcusd") ? "XBTUSD" : currpair; + return this.orderBookClient.getOrderbookItbit(newCurrpair); + } + + public Mono insertQuote(Mono quote) { + return this.myMongoRepository.insert(quote); + } + + public Mono currentQuote(String pair) { + final String newPair = this.currpairs.get(pair); + Query query = MongoUtils.buildCurrentQuery(Optional.of(newPair)); + return this.myMongoRepository.findOne(query, QuoteIb.class); + } + + public Flux tfQuotes(String timeFrame, String pair) { + final String newPair = this.currpairs.get(pair); + return this.serviceUtils.tfQuotes(timeFrame, newPair, QuoteIb.class, IB_HOUR_COL, IB_DAY_COL); + } + + public Mono pdfReport(String timeFrame, String pair) { + final String newPair = this.currpairs.get(pair); + return this.serviceUtils.pdfReport(timeFrame, newPair, QuoteIb.class, IB_HOUR_COL, IB_DAY_COL, this.reportMapper::convert); + } + + private void createIbHourlyAvg() { + LocalDateTime startAll = LocalDateTime.now(); + MyTimeFrame timeFrame = this.serviceUtils.createTimeFrame(IB_HOUR_COL, QuoteIb.class, true); + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + Calendar now = Calendar.getInstance(); + now.setTime(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + while (timeFrame.end().before(now)) { + Date start = new Date(); + Query query = new Query(); + query.addCriteria( + Criteria.where(DtoUtils.CREATEDAT).gt(timeFrame.begin().getTime()).lt(timeFrame.end().getTime())); + // Itbit + Mono> collectIb = this.myMongoRepository.find(query, QuoteIb.class) + .timeout(Duration.ofSeconds(5L)).doOnError(ex -> LOG.warn("Itbit prepare hour data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler) + .collectMultimap(quote -> quote.getPair(), quote -> quote) + .map(multimap -> multimap.keySet().stream() + .map(key -> makeIbQuoteHour(key, multimap, timeFrame.begin(), timeFrame.end())) + .collect(Collectors.toList())) + .flatMap(myList -> Mono + .just(myList.stream().flatMap(Collection::stream).collect(Collectors.toList()))); + collectIb.filter(Predicate.not(Collection::isEmpty)) + .flatMap(myColl -> this.myMongoRepository.insertAll(Mono.just(myColl), IB_HOUR_COL) + .timeout(Duration.ofSeconds(5L)) + .doOnError(ex -> LOG.warn("Itbit prepare hour data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler).collectList()) + .subscribeOn(this.mongoScheduler).block(); + + timeFrame.begin().add(Calendar.DAY_OF_YEAR, 1); + timeFrame.end().add(Calendar.DAY_OF_YEAR, 1); + LOG.info("Prepared Itbit Hour Data for: " + sdf.format(timeFrame.begin().getTime()) + " Time: " + + (new Date().getTime() - start.getTime()) + "ms"); + } + LOG.info(this.serviceUtils.createAvgLogStatement(startAll, "Prepared Itbit Hourly Data Time:")); + } + + private void createIbDailyAvg() { + LocalDateTime startAll = LocalDateTime.now(); + MyTimeFrame timeFrame = this.serviceUtils.createTimeFrame(IB_DAY_COL, QuoteIb.class, false); + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + Calendar now = Calendar.getInstance(); + now.setTime(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + while (timeFrame.end().before(now)) { + Date start = new Date(); + Query query = new Query(); + query.addCriteria( + Criteria.where(DtoUtils.CREATEDAT).gt(timeFrame.begin().getTime()).lt(timeFrame.end().getTime())); + // Itbit + Mono> collectIb = this.myMongoRepository.find(query, QuoteIb.class) + .timeout(Duration.ofSeconds(5L)).doOnError(ex -> LOG.warn("Itbit prepare day data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler) + .collectMultimap(quote -> quote.getPair(), quote -> quote) + .map(multimap -> multimap.keySet().stream() + .map(key -> makeIbQuoteDay(key, multimap, timeFrame.begin(), timeFrame.end())) + .collect(Collectors.toList())) + .flatMap(myList -> Mono + .just(myList.stream().flatMap(Collection::stream).collect(Collectors.toList()))); + collectIb.filter(Predicate.not(Collection::isEmpty)) + .flatMap(myColl -> this.myMongoRepository.insertAll(Mono.just(myColl), IB_DAY_COL) + .timeout(Duration.ofSeconds(5L)) + .doOnError(ex -> LOG.warn("Itbit prepare day data failed", ex)) + .onErrorResume(ex -> Mono.empty()).subscribeOn(this.mongoScheduler).collectList()) + .block(); + + timeFrame.begin().add(Calendar.DAY_OF_YEAR, 1); + timeFrame.end().add(Calendar.DAY_OF_YEAR, 1); + LOG.info("Prepared Itbit Day Data for: " + sdf.format(timeFrame.begin().getTime()) + " Time: " + + (new Date().getTime() - start.getTime()) + "ms"); + } + LOG.info(this.serviceUtils.createAvgLogStatement(startAll, "Prepared Itbit Daily Data Time:")); + } + + public Mono createIbAvg() { + Mono result = Mono.empty(); + if ((this.singleInstanceDeployment && !ItbitService.singleInstanceLock) || !this.singleInstanceDeployment) { + ItbitService.singleInstanceLock = true; + result = this.myMongoRepository.ensureIndex(IB_HOUR_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + IB_HOUR_COL + ") failed.", ex)) +// .doOnError(ex -> LOG.info("ensureIndex(" + IB_HOUR_COL + ") failed.", ex)) + .then(this.myMongoRepository.ensureIndex(IB_DAY_COL, DtoUtils.CREATEDAT) + .subscribeOn(this.mongoScheduler).timeout(Duration.ofMinutes(5L)) +// .doOnError(ex -> LOG.info("ensureIndex(" + IB_DAY_COL + ") failed.", ex)) + .onErrorContinue((ex, val) -> LOG.info("ensureIndex(" + IB_DAY_COL + ") failed.", ex))) + .map(value -> this.createHourDayAvg()).timeout(Duration.ofHours(2L)) +// .doOnError(ex -> LOG.info("createIbAvg() failed.", ex)) + .onErrorContinue((ex, val) -> LOG.info("createIbAvg() failed.", ex)) + .subscribeOn(this.mongoScheduler); + } + return result; + } + + private String createHourDayAvg() { + LOG.info("createHourDayAvg()"); + CompletableFuture future5 = CompletableFuture.supplyAsync(() -> { + this.createIbHourlyAvg(); + return "createIbHourlyAvg() Done."; + }, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS)); + CompletableFuture future6 = CompletableFuture.supplyAsync(() -> { + this.createIbDailyAvg(); + return "createIbDailyAvg() Done."; + }, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS)); + String combined = Stream.of(future5, future6).map(CompletableFuture::join).collect(Collectors.joining(" ")); + LOG.info(combined); + return "done"; + } + + private Collection makeIbQuoteHour(String key, Map> multimap, Calendar begin, + Calendar end) { + List hours = this.serviceUtils.createDayHours(begin); + List hourQuotes = new LinkedList(); + for (int i = 0; i < 24; i++) { + QuoteIb quoteIb = new QuoteIb(key, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, new Date()); + quoteIb.setCreatedAt(hours.get(i).getTime()); + final int x = i; + long count = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).count(); + if (count > 2) { + QuoteIb hourQuote = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(hours.get(x).getTime()) + && quote.getCreatedAt().before(hours.get(x + 1).getTime()); + }).reduce(quoteIb, (q1, q2) -> avgIbQuote(q1, q2, count)); + // hourQuote.setPair(key); + hourQuotes.add(hourQuote); + } + } + return hourQuotes; + } + + private Collection makeIbQuoteDay(String key, Map> multimap, Calendar begin, + Calendar end) { + List hourQuotes = new LinkedList(); + QuoteIb quoteIb = new QuoteIb(key, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, new Date()); + quoteIb.setCreatedAt(begin.getTime()); + long count = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).count(); + if (count > 2) { + QuoteIb hourQuote = multimap.get(key).stream().filter(quote -> { + return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); + }).reduce(quoteIb, (q1, q2) -> avgIbQuote(q1, q2, count)); + // hourQuote.setPair(key); + hourQuotes.add(hourQuote); + } + return hourQuotes; + } + + private QuoteIb avgIbQuote(QuoteIb q1, QuoteIb q2, long count) { + QuoteIb myQuote = new QuoteIb(q1.getPair(), this.serviceUtils.avgHourValue(q1.getBid(), q2.getBid(), count), + this.serviceUtils.avgHourValue(q1.getBidAmt(), q2.getBidAmt(), count), + this.serviceUtils.avgHourValue(q1.getAsk(), q2.getAsk(), count), + this.serviceUtils.avgHourValue(q1.getAskAmt(), q2.getAskAmt(), count), + this.serviceUtils.avgHourValue(q1.getLastPrice(), q2.getLastPrice(), count), + this.serviceUtils.avgHourValue(q1.getStAmt(), q2.getStAmt(), count), + this.serviceUtils.avgHourValue(q1.getVolume24h(), q2.getVolume24h(), count), + this.serviceUtils.avgHourValue(q1.getVolumeToday(), q2.getVolumeToday(), count), + this.serviceUtils.avgHourValue(q1.getHigh24h(), q2.getHigh24h(), count), + this.serviceUtils.avgHourValue(q1.getLow24h(), q2.getLow24h(), count), + this.serviceUtils.avgHourValue(q1.getOpenToday(), q2.getOpenToday(), count), + this.serviceUtils.avgHourValue(q1.getHighToday(), q2.getHighToday(), count), + this.serviceUtils.avgHourValue(q1.getLowToday(), q2.getLowToday(), count), + this.serviceUtils.avgHourValue(q1.getVwapToday(), q2.getVwapToday(), count), + this.serviceUtils.avgHourValue(q1.getVwap24h(), q2.getVwap24h(), count), q1.getServerTimeUTC()); + myQuote.setCreatedAt(q1.getCreatedAt()); + return myQuote; + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/JwtTokenService.java b/backend/src/main/java/ch/xxx/trader/usecase/services/JwtTokenService.java new file mode 100644 index 00000000..10da864f --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/JwtTokenService.java @@ -0,0 +1,187 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.time.Instant; +import java.util.Base64; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import ch.xxx.trader.domain.common.JwtUtils; +import ch.xxx.trader.domain.common.Role; +import ch.xxx.trader.domain.exceptions.AuthenticationException; +import ch.xxx.trader.domain.exceptions.JwtTokenValidationException; +import ch.xxx.trader.domain.model.entity.RevokedToken; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; + +@Component +public class JwtTokenService { + private static final Logger LOG = LoggerFactory.getLogger(JwtTokenService.class); + @Value("${security.jwt.token.secret-key}") + private String secretKey; + + @Value("${security.jwt.token.expire-length}") + private long validityInMilliseconds; // 24h + + private final List loggedOutUsers = new CopyOnWriteArrayList<>(); + + public record UserNameUuid(String userName, String uuid) { + } + + private Key jwtTokenKey; + + @PostConstruct + public void init() { + this.jwtTokenKey = Keys + .hmacShaKeyFor(Base64.getUrlDecoder().decode(secretKey.getBytes(StandardCharsets.ISO_8859_1))); + } + + public void updateLoggedOutUsers(List users) { + this.loggedOutUsers.clear(); + this.loggedOutUsers + .addAll(users.stream().map(myUser -> new UserNameUuid(myUser.getName(), myUser.getUuid())).toList()); + LOG.info("updateLoggedOutUsers: {}", this.loggedOutUsers.size()); + } + + public String createToken(String username, List roles) { + Claims claims = Jwts.claims().setSubject(username); + claims.put(JwtUtils.TOKENAUTHKEY, roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())) + .filter(Objects::nonNull).collect(Collectors.toList())); + claims.put(JwtUtils.TOKENLASTMSGKEY, new Date().getTime()); + claims.put(JwtUtils.UUID, UUID.randomUUID().toString()); + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + + return Jwts.builder().setClaims(claims).setIssuedAt(now).setExpiration(validity) + .signWith(this.jwtTokenKey, SignatureAlgorithm.HS256).compact(); + } + + public Optional> getClaims(Optional token) { + if (!token.isPresent()) { + return Optional.empty(); + } + return Optional.of(Jwts.parserBuilder().setSigningKey(this.jwtTokenKey).build().parseClaimsJws(token.get())); + } + + public UsernamePasswordAuthenticationToken getUserAuthenticationToken(String token) { + if (this.getAuthorities(token).stream().filter(role -> role.equals(Role.GUEST)).count() > 0) { + return new UsernamePasswordAuthenticationToken(this.getUsername(token), null); + } + return new UsernamePasswordAuthenticationToken(this.getUsername(token), "", this.getAuthorities(token)); + } + + @SuppressWarnings("unchecked") + public Collection getAuthorities(String token) { + Collection roles = new LinkedList<>(); + for (Role role : Role.values()) { + roles.add(role); + } + Collection> rolestrs = (Collection>) Jwts.parserBuilder() + .setSigningKey(this.jwtTokenKey).build().parseClaimsJws(token).getBody().get(JwtUtils.TOKENAUTHKEY); + return rolestrs.stream().map(str -> roles.stream() + .filter(r -> r.name().equals(str.getOrDefault(JwtUtils.AUTHORITY, ""))).findFirst().orElse(Role.GUEST)) + .collect(Collectors.toList()); + } + + public Authentication getAuthentication(String token) { + if (this.getAuthorities(token).stream().filter(role -> role.equals(Role.GUEST)).count() > 0) { + return new UsernamePasswordAuthenticationToken(this.getUsername(token), null); + } + return new UsernamePasswordAuthenticationToken(this.getUsername(token), "", this.getAuthorities(token)); + } + + public String getUsername(String token) { + return Jwts.parserBuilder().setSigningKey(this.jwtTokenKey).build().parseClaimsJws(token).getBody() + .getSubject(); + } + + public String getUuid(String token) { + this.validateToken(token); + return Jwts.parserBuilder().setSigningKey(this.jwtTokenKey).build().parseClaimsJws(token).getBody() + .get(JwtUtils.UUID, String.class); + } + + public String refreshToken(String token) { + validateToken(token); + Optional> claimsOpt = this.getClaims(Optional.of(token)); + if (claimsOpt.isEmpty()) { + throw new AuthenticationException("Invalid token claims"); + } + Claims claims = claimsOpt.get().getBody(); + claims.setIssuedAt(new Date()); + claims.setExpiration(new Date(Instant.now().toEpochMilli() + validityInMilliseconds)); + String newToken = Jwts.builder().setClaims(claims).signWith(this.jwtTokenKey, SignatureAlgorithm.HS256) + .compact(); + return newToken; + } + + public String resolveToken(HttpServletRequest req) { + String bearerToken = req.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7, bearerToken.length()); + } + return null; + } + + public Optional resolveToken(String bearerToken) { + if (bearerToken != null && bearerToken.startsWith(JwtUtils.BEARER)) { + return Optional.of(bearerToken.substring(7, bearerToken.length())); + } + return Optional.empty(); + } + + public boolean validateToken(String token) { + try { + Jws claimsJws = Jwts.parserBuilder().setSigningKey(this.jwtTokenKey).build().parseClaimsJws(token); + String subject = Optional.ofNullable(claimsJws.getBody().getSubject()) + .orElseThrow(() -> new AuthenticationException("Invalid JWT token")); + String uuid = Optional.ofNullable(claimsJws.getBody().get(JwtUtils.UUID, String.class)) + .orElseThrow(() -> new AuthenticationException("Invalid JWT token")); + // LOG.info("Subject: {}, Uuid: {}, LoggedOutUsers: {}", subject, uuid, + // JwtTokenService.loggedOutUsers.size()); + return this.loggedOutUsers.stream().noneMatch( + myUserName -> subject.equalsIgnoreCase(myUserName.userName()) && uuid.equals(myUserName.uuid())); + } catch (JwtException | IllegalArgumentException e) { + throw new JwtTokenValidationException("Expired or invalid JWT token", e); + } + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/MyAuthenticationProvider.java b/backend/src/main/java/ch/xxx/trader/usecase/services/MyAuthenticationProvider.java new file mode 100644 index 00000000..15e82fce --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/MyAuthenticationProvider.java @@ -0,0 +1,71 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.MyUser; + +@Component +public class MyAuthenticationProvider implements AuthenticationProvider { + private static final Logger log = LoggerFactory.getLogger(MyAuthenticationProvider.class); + private final MyMongoRepository myMongoRepository; + private final PasswordEncoder passwordEncoder; + + public MyAuthenticationProvider(MyMongoRepository myMongoRepository, PasswordEncoder passwordEncoder) { + this.myMongoRepository = myMongoRepository; + this.passwordEncoder = passwordEncoder; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String name = authentication.getName(); + String password = Optional.ofNullable(authentication.getCredentials()).map(pwd -> pwd.toString()).orElse(null); + Query query = new Query(); + query.addCriteria(Criteria.where("userId").is(name)); + MyUser user = this.myMongoRepository.findOne(query, MyUser.class).block(); + if(user == null) { + return new UsernamePasswordAuthenticationToken(null, null); + //throw new BadCredentialsException("User not found"); + } + if(!this.passwordEncoder.matches(password, user.getPassword())) { + return new UsernamePasswordAuthenticationToken(null, null); + //throw new AuthenticationCredentialsNotFoundException("User: "+name+" not found."); + } + log.info("User: "+name+" logged in."); + return new UsernamePasswordAuthenticationToken( + name, password, user.getAuthorities()); + } + + @Override + public boolean supports(Class authentication) { + return authentication.equals( + UsernamePasswordAuthenticationToken.class); + } + +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceBean.java b/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceBean.java new file mode 100644 index 00000000..0892e7ff --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceBean.java @@ -0,0 +1,186 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.security.crypto.password.PasswordEncoder; + +import ch.xxx.trader.domain.common.JwtUtils; +import ch.xxx.trader.domain.common.PasswordEncryption; +import ch.xxx.trader.domain.common.Role; +import ch.xxx.trader.domain.common.WebUtils; +import ch.xxx.trader.domain.exceptions.AuthenticationException; +import ch.xxx.trader.domain.model.dto.AuthCheck; +import ch.xxx.trader.domain.model.dto.RefreshTokenDto; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.MyUser; +import ch.xxx.trader.domain.model.entity.RevokedToken; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import reactor.core.Disposable; +import reactor.core.publisher.Mono; + +public class MyUserServiceBean { + private static final Logger LOGGER = LoggerFactory.getLogger(MyUserServiceBean.class); + private static final long LOGOUT_TIMEOUT = 185L; + protected final JwtTokenService jwtTokenService; + private final PasswordEncryption passwordEncryption; + protected final MyMongoRepository myMongoRepository; + private final PasswordEncoder passwordEncoder; + + private Disposable updateLoggedOutUsersDisposable = Mono.empty().subscribe(); + + public MyUserServiceBean(JwtTokenService jwtTokenProvider, PasswordEncoder passwordEncoder, + PasswordEncryption passwordEncryption, MyMongoRepository myMongoRepository) { + this.jwtTokenService = jwtTokenProvider; + this.passwordEncryption = passwordEncryption; + this.myMongoRepository = myMongoRepository; + this.passwordEncoder = passwordEncoder; + } + + public void updateLoggedOutUsers() { + this.updateLoggedOutUsersDisposable.dispose(); + this.updateLoggedOutUsersDisposable = this.myMongoRepository.find(new Query(), RevokedToken.class).collectList() + .flatMapIterable(revokedTokens -> { + this.jwtTokenService.updateLoggedOutUsers(revokedTokens.stream() +// .filter(StreamHelpers.distinctByKey(value -> value.getUuid())) + .filter(myRevokedToken -> myRevokedToken.getLastLogout() == null || !myRevokedToken + .getLastLogout().isBefore(LocalDateTime.now().minusSeconds(LOGOUT_TIMEOUT))) + .toList()); + return revokedTokens; + }) + .filter(myRevokedToken -> myRevokedToken.getLastLogout() != null + && myRevokedToken.getLastLogout().isBefore(LocalDateTime.now().minusSeconds(LOGOUT_TIMEOUT))) + .flatMap(revokeToken -> this.myMongoRepository.remove(Mono.just(revokeToken))).subscribe(); + } + + public Mono postAuthorize(AuthCheck authcheck, Map header) { + Optional token = WebUtils.extractToken(header); + Query query = new Query(); + query.addCriteria(Criteria.where("salt").is(authcheck.getHash())); + return this.myMongoRepository.findOne(query, MyUser.class).switchIfEmpty(Mono.just(new MyUser())) + .map(user -> mapMyUser(user, authcheck, token)); + } + + private AuthCheck mapMyUser(MyUser myUser, AuthCheck authcheck, Optional token) { + Optional> claims = this.jwtTokenService.getClaims(token); + if (myUser.getUserId() != null && claims.isPresent() + && myUser.getUserId().equals(claims.get().getBody().getSubject()) + && new Date().before(claims.get().getBody().getExpiration())) { + return new AuthCheck(authcheck.getHash(), authcheck.getPath(), true); + } + return new AuthCheck(authcheck.getHash(), authcheck.getPath(), false); + } + + public Mono postUserSignin(MyUser myUser, boolean persist, boolean check) { + Query query = new Query(Criteria.where("userId").is(myUser.getUserId())); + return check ? this.myMongoRepository.findOne(query, MyUser.class).switchIfEmpty(Mono.just(myUser)) + .flatMap(myUser1 -> signinHelp(myUser1, persist)) : this.saveSignin(myUser); + } + + private Mono signinHelp(MyUser myUser1, boolean persist) { + if (myUser1.get_id() == null) { + String salt; + try { + salt = this.passwordEncryption.generateSalt(); + } catch (NoSuchAlgorithmException e) { + throw new AuthenticationException("Generating salt failed.", e); + } + String encryptedPassword = this.passwordEncoder.encode(myUser1.getPassword()); + myUser1.setPassword(encryptedPassword); + myUser1.setSalt(salt); + return persist ? saveSignin(myUser1) : Mono.just(myUser1); + } + return Mono.just(new MyUser()); + } + + private Mono saveSignin(MyUser myUser1) { + return this.myMongoRepository.save(myUser1).flatMap(myUser2 -> { + myUser2.setPassword("XXX"); + myUser2.setSalt("YYY"); + return Mono.just(myUser2); + }); + } + + public Mono postLogout(String bearerStr) { + String username = getTokenUsername(bearerStr); + String uuid = getTokenUuid(bearerStr); + Query query = new Query(Criteria.where("uuid").is(uuid)); + return this.myMongoRepository.find(query, RevokedToken.class) + .filter(myRevokedToken -> myRevokedToken.getUuid().equals(uuid)).collectList() + .doOnEach(myRevokedTokens -> { + if (myRevokedTokens.hasValue() && !myRevokedTokens.get().isEmpty()) + LOGGER.warn("Duplicate logout for user {}", username); + }) + .flatMap(myRevokedTokens -> !myRevokedTokens.isEmpty() ? Mono.just(Boolean.TRUE) + : this.myMongoRepository + .insert(Mono.just(new RevokedToken(null, username, uuid, LocalDateTime.now()))) + .then(Mono.just(Boolean.TRUE))); + } + + protected String getTokenUuid(String bearerStr) { + return this.jwtTokenService.getUuid(JwtUtils.resolveToken(bearerStr) + .orElseThrow(() -> new AuthenticationException("Invalid bearer string."))); + } + + protected String getTokenUsername(String bearerStr) { + return this.jwtTokenService.getUsername(JwtUtils.resolveToken(bearerStr) + .orElseThrow(() -> new AuthenticationException("Invalid bearer string."))); + } + + + public Mono postUserLogin(MyUser myUser) throws NoSuchAlgorithmException, InvalidKeySpecException { + Query query = new Query(); + query.addCriteria(Criteria.where("userId").is(myUser.getUserId())); + return this.myMongoRepository.findOne(query, MyUser.class).switchIfEmpty(Mono.just(new MyUser())) + .delayElement(Duration.ofSeconds(3L)) + .map(user1 -> loginHelp(user1, myUser.getPassword())); + } + + private MyUser loginHelp(MyUser user, String passwd) { + if (user.getUserId() != null) { + if (this.passwordEncoder.matches(passwd, user.getPassword())) { + String jwtToken = this.jwtTokenService.createToken(user.getUserId(), Arrays.asList(Role.USERS)); + user.setToken(jwtToken); + user.setPassword("XXX"); + return user; + } + } + return new MyUser(); + } + + public Mono refreshToken(String bearerStr) { + Optional tokenOpt = this.jwtTokenService.resolveToken(bearerStr); + if (tokenOpt.isEmpty()) { + throw new AuthenticationException("Invalid token"); + } + String newToken = this.jwtTokenService.refreshToken(tokenOpt.get()); + LOGGER.info("Jwt Token refreshed."); + return Mono.just(new RefreshTokenDto(newToken)); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceDb.java b/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceDb.java new file mode 100644 index 00000000..9994bfe3 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceDb.java @@ -0,0 +1,44 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.PasswordEncryption; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.MyUser; +import ch.xxx.trader.domain.services.MyUserService; +import reactor.core.publisher.Mono; + +@Profile("!kafka & !prod") +@Service +public class MyUserServiceDb extends MyUserServiceBean implements MyUserService { + private static final Logger LOGGER = LoggerFactory.getLogger(MyUserServiceDb.class); + + public MyUserServiceDb(JwtTokenService jwtTokenProvider, PasswordEncoder passwordEncoder, + PasswordEncryption passwordEncryption, MyMongoRepository myMongoRepository) { + super(jwtTokenProvider, passwordEncoder, passwordEncryption, myMongoRepository); + } + + @Override + public Mono postUserSignin(MyUser myUser) { + return super.postUserSignin(myUser, true, true); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceEvents.java b/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceEvents.java new file mode 100644 index 00000000..b03983d7 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/MyUserServiceEvents.java @@ -0,0 +1,107 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.PasswordEncryption; +import ch.xxx.trader.domain.model.dto.RevokedTokensDto; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.MyUser; +import ch.xxx.trader.domain.model.entity.RevokedToken; +import ch.xxx.trader.domain.services.MyEventProducer; +import ch.xxx.trader.domain.services.MyUserService; +import reactor.core.publisher.ConnectableFlux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; + +@Profile("kafka | prod") +@Service +public class MyUserServiceEvents extends MyUserServiceBean implements MyUserService { + private static final Logger LOGGER = LoggerFactory.getLogger(MyUserServiceEvents.class); +// private static final long LOGOUT_TIMEOUT = 95L; + private final MyEventProducer myEventProducer; + private final Sinks.Many myUserSink = Sinks.many().multicast().onBackpressureBuffer(); + private final ConnectableFlux myUserFlux = this.myUserSink.asFlux().publish(); + + public MyUserServiceEvents(JwtTokenService jwtTokenProvider, PasswordEncoder passwordEncoder, + PasswordEncryption passwordEncryption, MyMongoRepository myMongoRepository, + MyEventProducer myEventProducer) { + super(jwtTokenProvider, passwordEncoder, passwordEncryption, myMongoRepository); + this.myEventProducer = myEventProducer; + } + + @Override + public void updateLoggedOutUsers() { + // do nothing + } + + public Boolean updateLoggedOutUsers(List revokedTokens) { + this.jwtTokenService.updateLoggedOutUsers(revokedTokens); + return Boolean.TRUE; + } + + @Override + public Mono postUserSignin(MyUser myUser) { + Mono myUserResult = this.myUserFlux.autoConnect() + .filter(myUser1 -> myUser.getUserId().equalsIgnoreCase(myUser1.getUserId())).shareNext(); + return super.postUserSignin(myUser, false, true).flatMap(dto -> this.myEventProducer.sendNewUser(dto)) + .zipWith(myUserResult, (myUser1, msgMyUser1) -> msgMyUser1).flatMap(myUser1 -> { + // LOGGER.info("MyUser signin result: {}",myUser1); + return Mono.just(myUser1); + }); + } + + public Mono userSigninEvent(Optional myUserOpt) { + return myUserOpt.stream() + .flatMap(myUser -> Stream.of(super.postUserSignin(myUser, true, false).flatMap(myUser1 -> { + if (this.myUserSink.tryEmitNext(myUser1).isFailure()) { + LOGGER.info("Emit to myUserSink failed. {}", myUser1); + } + return Mono.just(myUser1); + }))).findFirst().orElse(Mono.empty()); + } + + @Override + public Mono postLogout(String token) { + String username = this.getTokenUsername(token); + String uuid = this.getTokenUuid(token); + List revokedTokens = new ArrayList<>(); + revokedTokens.add(new RevokedToken(null, username, uuid, LocalDateTime.now())); + return revokedTokens.stream() + .map(myRevokedToken -> this.myEventProducer.sendUserLogout(myRevokedToken) + .flatMap(value -> Mono.just(value != null))) + .reduce((result1, result2) -> Mono.just(result1.block() == true && result2.block() == true)) + .orElse(Mono.just(Boolean.FALSE)); + } + + public Mono logoutEvent(Optional revokedTokensDtoOpt) { + return revokedTokensDtoOpt.stream() + .flatMap(revokedTokensDto -> Stream + .of(Mono.just(this.updateLoggedOutUsers(revokedTokensDto.getRevokedTokens())))) + .findFirst().orElse(Mono.empty()); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/ReportGenerator.java b/backend/src/main/java/ch/xxx/trader/usecase/services/ReportGenerator.java new file mode 100644 index 00000000..cb1c6477 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/ReportGenerator.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.io.ByteArrayOutputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.model.dto.QuotePdf; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JasperCompileManager; +import net.sf.jasperreports.engine.JasperFillManager; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.JasperReport; +import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; +import net.sf.jasperreports.engine.export.JRPdfExporter; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +@Service +public class ReportGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(ReportGenerator.class); + //volatile to protect concurrent read/write accesses + private static volatile JasperReport jasperReport = null; + //limit cpu load to max 2 concurrently generated reports + private final Scheduler mongoScheduler = Schedulers.newBoundedElastic(2, 100, "reports", 10); + + public Mono generateReport(Flux quotes) { + return quotes.publishOn(mongoScheduler).collectList().map(quotePdfs -> { + byte[] result = new byte[0]; + Date start = new Date(); + try { + if (jasperReport == null) { + jasperReport = JasperCompileManager.compileReport( + this.getClass().getClassLoader().getResourceAsStream("currencyReport.jrxml")); + LOGGER.info("Report compiled in: " + (new Date().getTime() - start.getTime()) + "ms"); + } + Map params = new HashMap<>(); + params.put("quotes", new JRBeanCollectionDataSource(quotePdfs)); + JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, params, + new JRBeanCollectionDataSource(quotePdfs)); + + JRPdfExporter pdfExporter = new JRPdfExporter(); + pdfExporter.setExporterInput(new SimpleExporterInput(jasperPrint)); + ByteArrayOutputStream pdfReportStream = new ByteArrayOutputStream(); + pdfExporter.setExporterOutput(new SimpleOutputStreamExporterOutput(pdfReportStream)); + pdfExporter.exportReport(); + + result = pdfReportStream.toByteArray(); + } catch (JRException e) { + LOGGER.error("Report generation failed.", e); + } + LOGGER.info("Report generated in: " + (new Date().getTime() - start.getTime()) + "ms"); + return result; + }); + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/ServiceUtils.java b/backend/src/main/java/ch/xxx/trader/usecase/services/ServiceUtils.java new file mode 100644 index 00000000..b6778dab --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/ServiceUtils.java @@ -0,0 +1,204 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.MongoUtils; +import ch.xxx.trader.domain.common.MongoUtils.TimeFrame; +import ch.xxx.trader.domain.model.dto.QuotePdf; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.Quote; +import ch.xxx.trader.usecase.common.DtoUtils; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Service +public class ServiceUtils { + public record MyTimeFrame(Calendar begin, Calendar end) { + } + + private final MyMongoRepository myMongoRepository; + private final ReportGenerator reportGenerator; + + public ServiceUtils(MyMongoRepository myMongoRepository, ReportGenerator reportGenerator) { + this.myMongoRepository = myMongoRepository; + this.reportGenerator = reportGenerator; + } + + public List createDayHours(Calendar begin) { + List hours = new LinkedList(); + Calendar cal = Calendar.getInstance(); + cal.setTime(begin.getTime()); + while (hours.size() <= 24) { + Calendar myCal = Calendar.getInstance(); + myCal.setTime(cal.getTime()); + hours.add(myCal); + cal.add(Calendar.HOUR_OF_DAY, 1); + } + return hours; + } + + public BigDecimal avgHourValue(BigDecimal v1, BigDecimal v2, long count) { + return v1.add(v2 == null ? BigDecimal.ZERO + : v2.divide(BigDecimal.valueOf(count == 0 ? 1 : count), 10, RoundingMode.HALF_UP)); + } + + public List showThreads() { + List logs = new LinkedList<>(); + Set threads = Thread.getAllStackTraces().keySet(); + logs.add(String.format("%-15s \t %-15s \t %-15s \t %s \t %s \t %s", "Name", "State", "Priority", "isDaemon", + "TheadGroup", "ThreadGroupActive")); + for (Thread t : threads) { + logs.add(String.format("%-15s \t %-15s \t %-15d \t %s \t %s \t %d", t.getName(), t.getState(), + t.getPriority(), t.isDaemon(), t.getThreadGroup().getName(), t.getThreadGroup().activeCount())); + } + return logs; + } + + public String createAvgLogStatement(LocalDateTime start, String statementStart) { + Duration myDuration = Duration.between(start.atZone(ZoneId.systemDefault()).toInstant(), + LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()); + long millis = (myDuration.toSeconds() < 1 ? myDuration.toMillis() + : (myDuration.toMillis() - myDuration.toSeconds() * 1000)); + return String.format("%s %d.%d seconds.", statementStart, myDuration.toSeconds(), millis); + } + + public Flux tfQuotes(String timeFrame, String pair, Class quoteClass, String hourCol, + String dayCol) { + TimeFrame myTimeFrame = MongoUtils.KEY_TO_TIMEFRAME.get(timeFrame); + Flux result = switch (myTimeFrame) { + case MongoUtils.TimeFrame.TODAY -> { + Query query = MongoUtils.buildTodayQuery(Optional.of(pair)); + yield this.myMongoRepository.find(query, quoteClass) + .filter(q -> MongoUtils.filterEvenMinutes(q.getCreatedAt())); + } + case MongoUtils.TimeFrame.SEVENDAYS -> { + Query query = MongoUtils.build7DayQuery(Optional.of(pair)); + yield this.myMongoRepository.find(query, quoteClass, hourCol); + } + case MongoUtils.TimeFrame.THIRTYDAYS -> { + Query query = MongoUtils.build30DayQuery(Optional.of(pair)); + yield this.myMongoRepository.find(query, quoteClass, dayCol); + } + case MongoUtils.TimeFrame.NINTYDAYS -> { + Query query = MongoUtils.build90DayQuery(Optional.of(pair)); + yield this.myMongoRepository.find(query, quoteClass, dayCol); + } + case MongoUtils.TimeFrame.Month6 -> { + Query query = MongoUtils.buildTimeFrameQuery(Optional.of(pair), myTimeFrame); + yield this.myMongoRepository.find(query, quoteClass, dayCol); + } + case MongoUtils.TimeFrame.Year1 -> { + Query query = MongoUtils.buildTimeFrameQuery(Optional.of(pair), myTimeFrame); + yield this.myMongoRepository.find(query, quoteClass, dayCol); + } + default -> Flux.empty(); + }; + return result; + } + + public Mono pdfReport(String timeFrame, String pair, Class quoteClass, String hourCol, + String dayCol, Function mapping) { + TimeFrame myTimeFrame = MongoUtils.KEY_TO_TIMEFRAME.get(timeFrame); + Mono result = switch (myTimeFrame) { + case MongoUtils.TimeFrame.TODAY -> { + Query query = MongoUtils.buildTodayQuery(Optional.of(pair)); + yield this.reportGenerator.generateReport(this.myMongoRepository.find(query, quoteClass) + .filter(myQuote -> MongoUtils.filter10Minutes(myQuote.getCreatedAt())).map(mapping)); + } + case MongoUtils.TimeFrame.SEVENDAYS -> { + Query query = MongoUtils.build7DayQuery(Optional.of(pair)); + yield this.reportGenerator + .generateReport(this.myMongoRepository.find(query, quoteClass, hourCol).map(mapping)); + } + case MongoUtils.TimeFrame.THIRTYDAYS -> { + Query query = MongoUtils.build30DayQuery(Optional.of(pair)); + yield this.reportGenerator + .generateReport(this.myMongoRepository.find(query, quoteClass, dayCol).map(mapping)); + } + case MongoUtils.TimeFrame.NINTYDAYS -> { + Query query = MongoUtils.build90DayQuery(Optional.of(pair)); + yield this.reportGenerator + .generateReport(this.myMongoRepository.find(query, quoteClass, dayCol).map(mapping)); + } + case MongoUtils.TimeFrame.Month6 -> { + Query query = MongoUtils.buildTimeFrameQuery(Optional.of(pair), myTimeFrame); + yield this.reportGenerator + .generateReport(this.myMongoRepository.find(query, quoteClass, dayCol).map(mapping)); + } + case MongoUtils.TimeFrame.Year1 -> { + Query query = MongoUtils.buildTimeFrameQuery(Optional.of(pair), myTimeFrame); + yield this.reportGenerator + .generateReport(this.myMongoRepository.find(query, quoteClass, dayCol).map(mapping)); + } + default -> Mono.empty(); + }; + return result; + } + + public MyTimeFrame createTimeFrame(String colName, Class colType, boolean hour) { + if (!this.myMongoRepository.collectionExists(colName).block()) { + this.myMongoRepository.createCollection(colName).block(); + } + Query query = new Query().with(Sort.by(DtoUtils.CREATEDAT).ascending()); + Optional firstQuote = Optional + .ofNullable(this.myMongoRepository.findOne(query, colType).block()); + query = new Query().with(Sort.by(DtoUtils.CREATEDAT).descending()); + final Calendar globalBeginn = Calendar.getInstance(); + Optional.ofNullable(this.myMongoRepository.findOne(query, colType, colName).block()) + .ifPresentOrElse(myQuote -> { + this.calcGlobalBegin(hour, globalBeginn, myQuote); + }, () -> { + globalBeginn.setTime(firstQuote.stream().map(myQuote -> myQuote.getCreatedAt()).findFirst().orElse( + Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()))); + }); + + Calendar begin = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + begin.setTime(globalBeginn.getTime()); + begin.set(Calendar.MINUTE, 0); + begin.set(Calendar.SECOND, 0); + end.setTime(begin.getTime()); + end.add(Calendar.DAY_OF_YEAR, 1); + return new MyTimeFrame(begin, end); + } + + private void calcGlobalBegin(boolean hour, Calendar globalBeginn, Quote myQuote) { + globalBeginn.setTime(myQuote.getCreatedAt()); + if (hour) { + globalBeginn.add(Calendar.HOUR_OF_DAY, 1); + } else { + globalBeginn.add(Calendar.DAY_OF_YEAR, 1); + } + } +} diff --git a/backend/src/main/java/ch/xxx/trader/usecase/services/StatisticService.java b/backend/src/main/java/ch/xxx/trader/usecase/services/StatisticService.java new file mode 100644 index 00000000..434c1072 --- /dev/null +++ b/backend/src/main/java/ch/xxx/trader/usecase/services/StatisticService.java @@ -0,0 +1,192 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.springframework.stereotype.Service; + +import ch.xxx.trader.domain.common.MongoUtils; +import ch.xxx.trader.domain.common.MongoUtils.TimeFrame; +import ch.xxx.trader.domain.model.dto.CommonStatisticsDto; +import ch.xxx.trader.domain.model.dto.RangeDto; +import ch.xxx.trader.domain.model.dto.StatisticsCommon.CoinExchange; +import ch.xxx.trader.domain.model.dto.StatisticsCommon.StatisticsCurrPair; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.Quote; +import ch.xxx.trader.domain.model.entity.QuoteBf; +import ch.xxx.trader.domain.model.entity.QuoteBs; +import reactor.core.publisher.Mono; + +@Service +public class StatisticService { + private final MyMongoRepository myMongoRepository; + + public StatisticService(MyMongoRepository myMongoRepository) { + this.myMongoRepository = myMongoRepository; + } + + public Mono getCommonStatistics(StatisticsCurrPair currPair, CoinExchange coinExchange) { + Mono result = CoinExchange.Bitfinex.equals(coinExchange) + ? this.createBitfinexStatistics(currPair) + : this.createBitStampStatistics(currPair); + return result; + } + + private Mono createBitStampStatistics(StatisticsCurrPair currPair) { + Mono result = this.myMongoRepository + .find(MongoUtils.buildTimeFrameQuery(Optional.of(currPair.getBitStampKey()), TimeFrame.Year5, 5000), + QuoteBs.class, BitstampService.BS_DAY_COL) + .collectList().flatMap(StatisticService::calcStatistics).map(value -> { + value.setCurrPair(currPair); + return value; + }); + return result; + } + + private static Mono calcStatistics(List quotes) { + CommonStatisticsDto commonStatisticsDto = new CommonStatisticsDto(); + calcStatistics1Month(quotes, commonStatisticsDto); + calcStatistics3Months(quotes, commonStatisticsDto); + calcStatistics6Months(quotes, commonStatisticsDto); + calcStatistics1Year(quotes, commonStatisticsDto); + calcStatistics2Years(quotes, commonStatisticsDto); + calcStatistics5Years(quotes, commonStatisticsDto); + return Mono.just(commonStatisticsDto); + } + + static void calcStatistics5Years(List quotes, CommonStatisticsDto commonStatisticsDto) { + List quotes5Year = quotes.stream() + .filter(myQuote -> myQuote.getCreatedAt().after(StatisticService.createBeforeDate(0, 5))).toList(); + commonStatisticsDto.setRange5Year( + new RangeDto(StatisticService.getMinMaxValue(quotes5Year, false), StatisticService.getMinMaxValue(quotes5Year, true))); + commonStatisticsDto.setPerformance5Year(StatisticService.calcPerformance(quotes5Year)); + commonStatisticsDto.setAvgVolume5Year(StatisticService.calcAvgVolume(quotes5Year)); + commonStatisticsDto.setVolatility5Year(StatisticService.calcVolatility(quotes5Year)); + } + + static void calcStatistics2Years(List quotes, CommonStatisticsDto commonStatisticsDto) { + List quotes2Year = quotes.stream() + .filter(myQuote -> myQuote.getCreatedAt().after(StatisticService.createBeforeDate(0, 2))).toList(); + commonStatisticsDto.setRange2Year( + new RangeDto(StatisticService.getMinMaxValue(quotes2Year, false), StatisticService.getMinMaxValue(quotes2Year, true))); + commonStatisticsDto.setPerformance2Year(StatisticService.calcPerformance(quotes2Year)); + commonStatisticsDto.setAvgVolume2Year(StatisticService.calcAvgVolume(quotes2Year)); + commonStatisticsDto.setVolatility2Year(StatisticService.calcVolatility(quotes2Year)); + } + + static void calcStatistics1Year(List quotes, CommonStatisticsDto commonStatisticsDto) { + List quotes1Year = quotes.stream() + .filter(myQuote -> myQuote.getCreatedAt().after(StatisticService.createBeforeDate(0, 1))).toList(); + commonStatisticsDto.setRange1Year( + new RangeDto(StatisticService.getMinMaxValue(quotes1Year, false), StatisticService.getMinMaxValue(quotes1Year, true))); + commonStatisticsDto.setPerformance1Year(StatisticService.calcPerformance(quotes1Year)); + commonStatisticsDto.setAvgVolume1Year(StatisticService.calcAvgVolume(quotes1Year)); + commonStatisticsDto.setVolatility1Year(StatisticService.calcVolatility(quotes1Year)); + } + + static void calcStatistics6Months(List quotes, CommonStatisticsDto commonStatisticsDto) { + List quotes6Month = quotes.stream() + .filter(myQuote -> myQuote.getCreatedAt().after(StatisticService.createBeforeDate(6, 0))).toList(); + commonStatisticsDto.setPerformance6Month(StatisticService.calcPerformance(quotes6Month)); + commonStatisticsDto.setAvgVolume6Month(StatisticService.calcAvgVolume(quotes6Month)); + commonStatisticsDto.setVolatility6Month(StatisticService.calcVolatility(quotes6Month)); + commonStatisticsDto.setRange6Month( + new RangeDto(StatisticService.getMinMaxValue(quotes6Month, false), StatisticService.getMinMaxValue(quotes6Month, true))); + } + + static void calcStatistics3Months(List quotes, CommonStatisticsDto commonStatisticsDto) { + List quotes3Month = quotes.stream() + .filter(myQuote -> myQuote.getCreatedAt().after(StatisticService.createBeforeDate(3, 0))).toList(); + commonStatisticsDto.setRange3Month( + new RangeDto(StatisticService.getMinMaxValue(quotes3Month, false), StatisticService.getMinMaxValue(quotes3Month, true))); + commonStatisticsDto.setPerformance3Month(StatisticService.calcPerformance(quotes3Month)); + commonStatisticsDto.setAvgVolume3Month(StatisticService.calcAvgVolume(quotes3Month)); + commonStatisticsDto.setVolatility3Month(StatisticService.calcVolatility(quotes3Month)); + } + + static void calcStatistics1Month(List quotes, CommonStatisticsDto commonStatisticsDto) { + Date beforeDate = StatisticService.createBeforeDate(1, 0); + List quotes1Month = quotes.stream() + .filter(myQuote -> myQuote.getCreatedAt().after(beforeDate)).toList(); + commonStatisticsDto.setRange1Month( + new RangeDto(StatisticService.getMinMaxValue(quotes1Month, false), StatisticService.getMinMaxValue(quotes1Month, true))); + commonStatisticsDto.setAvgVolume1Month(StatisticService.calcAvgVolume(quotes1Month)); + commonStatisticsDto.setPerformance1Month(StatisticService.calcPerformance(quotes1Month)); + commonStatisticsDto.setVolatility1Month(StatisticService.calcVolatility(quotes1Month)); + } + + private static BigDecimal calcVolatility(List quotes) { + final BigDecimal average = quotes.size() < 3 ? BigDecimal.ZERO + : quotes.stream().map(myQuote -> StatisticService.getLastValue(myQuote)) + .reduce(BigDecimal.ZERO, (acc, value) -> acc.add(value)) + .divide(BigDecimal.valueOf(quotes.size()), MathContext.DECIMAL128); + BigDecimal variance = quotes.size() < 3 ? BigDecimal.ZERO : quotes.stream().map(myQuote -> StatisticService.getLastValue(myQuote)).map(lastValue -> lastValue.subtract(average)) + .map(avgDifference -> avgDifference.multiply(avgDifference)) + .reduce(BigDecimal.ZERO, (acc, value) -> acc.add(value)).divide(BigDecimal.valueOf(quotes.size()), MathContext.DECIMAL128); + BigDecimal volatility = variance.sqrt(MathContext.DECIMAL128); + return volatility; + } + + private static BigDecimal calcAvgVolume(List quotes) { + return quotes.size() < 3 ? BigDecimal.ZERO + : quotes.stream().map(myQuote -> StatisticService.getVolume(myQuote)) + .reduce(BigDecimal.ZERO, (acc, value) -> acc.add(value)) + .divide(BigDecimal.valueOf(quotes.size()), MathContext.DECIMAL128); + } + + private static BigDecimal getVolume(T myQuote) { + return myQuote instanceof QuoteBs ? ((QuoteBs) myQuote).getLast() : ((QuoteBf) myQuote).getLast_price(); + } + + private static Double calcPerformance(List quotes) { + return quotes.size() < 3 ? 0.0 + : ((StatisticService.getLastValue(quotes.get(quotes.size()-1)).doubleValue() / StatisticService.getLastValue(quotes.get(0)).doubleValue()) - 1) * 100; + } + + private static BigDecimal getMinMaxValue(List quotes, boolean max) { + Stream valueStream = quotes.stream().map(myQuote -> StatisticService.getLastValue(myQuote)); + return max ? valueStream.max(BigDecimal::compareTo).orElse(BigDecimal.ZERO) + : valueStream.min(BigDecimal::compareTo).orElse(BigDecimal.ZERO); + } + + private static BigDecimal getLastValue(T myQuote) { + return myQuote instanceof QuoteBs ? ((QuoteBs) myQuote).getLast() : ((QuoteBf) myQuote).getLast_price(); + } + + private static Date createBeforeDate(int months, int years) { + return Date.from(LocalDate.now().minusMonths(months).minusYears(years).atStartOfDay() + .atZone(ZoneId.systemDefault()).toInstant()); + } + + private Mono createBitfinexStatistics(StatisticsCurrPair currPair) { + Mono result = this.myMongoRepository + .find(MongoUtils.buildTimeFrameQuery(Optional.of(currPair.getBitfinexKey()), TimeFrame.Year5, 5000), + QuoteBf.class, BitfinexService.BF_DAY_COL) + .collectList().flatMap(StatisticService::calcStatistics).map(value -> { + value.setCurrPair(currPair); + return value; + }); + return result; + } +} diff --git a/backend/src/main/java/org/apache/kafka/clients/DefaultHostResolver.java b/backend/src/main/java/org/apache/kafka/clients/DefaultHostResolver.java new file mode 100644 index 00000000..8d58e2ac --- /dev/null +++ b/backend/src/main/java/org/apache/kafka/clients/DefaultHostResolver.java @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package org.apache.kafka.clients; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class DefaultHostResolver implements HostResolver { + public static volatile String IP_ADDRESS = ""; + public static volatile String KAFKA_SERVER_NAME = ""; + public static volatile String KAFKA_SERVICE_NAME = ""; + + @Override + public InetAddress[] resolve(String host) throws UnknownHostException { + if(host.startsWith(KAFKA_SERVER_NAME) && !IP_ADDRESS.isBlank()) { + InetAddress[] addressArr = new InetAddress[1]; + addressArr[0] = InetAddress.getByAddress(host, InetAddress.getByName(IP_ADDRESS).getAddress()); + return addressArr; + } else if(host.startsWith(KAFKA_SERVER_NAME) && !KAFKA_SERVICE_NAME.isBlank()) { + host = KAFKA_SERVICE_NAME; + } + return InetAddress.getAllByName(host); + } + +} diff --git a/backend/src/main/resources/.gitignore b/backend/src/main/resources/.gitignore new file mode 100644 index 00000000..b6edfe90 --- /dev/null +++ b/backend/src/main/resources/.gitignore @@ -0,0 +1 @@ +/static/ diff --git a/backend/src/main/resources/application-kafka.properties b/backend/src/main/resources/application-kafka.properties new file mode 100644 index 00000000..2e4a8e0e --- /dev/null +++ b/backend/src/main/resources/application-kafka.properties @@ -0,0 +1,17 @@ +kafka.server.name=${KAFKA_SERVER_NAME:localhost} +spring.kafka.bootstrap-servers=localhost:9092 +spring.kafka.producer.compression-type=gzip +spring.kafka.producer.transaction-id-prefix: tx- +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.enable.idempotence=true +spring.kafka.consumer.group-id=group_id +spring.kafka.consumer.auto-offset-reset=earliest +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.enable-auto-commit=false +spring.kafka.consumer.isolation-level=read_committed +spring.kafka.consumer.transaction-id-prefix: tx- +spring.kafka.streams.properties.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde +spring.kafka.streams.properties.default.value.serde=org.apache.kafka.common.serialization.Serdes$StringSerde +spring.kafka.streams.application-id=angular-and-spring \ No newline at end of file diff --git a/backend/src/main/resources/application-prod.properties b/backend/src/main/resources/application-prod.properties new file mode 100644 index 00000000..60fb9d6b --- /dev/null +++ b/backend/src/main/resources/application-prod.properties @@ -0,0 +1,17 @@ +kafka.server.name=${KAFKA_SERVER_NAME:kafkaapp} +spring.kafka.bootstrap-servers=${KAFKA_SERVICE_NAME}:9092 +spring.kafka.producer.compression-type=gzip +spring.kafka.producer.transaction-id-prefix: tx- +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.enable.idempotence=true +spring.kafka.consumer.group-id=group_id +spring.kafka.consumer.auto-offset-reset=earliest +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.enable-auto-commit=false +spring.kafka.consumer.isolation-level=read_committed +spring.kafka.consumer.transaction-id-prefix: tx- +spring.kafka.streams.properties.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde +spring.kafka.streams.properties.default.value.serde=org.apache.kafka.common.serialization.Serdes$StringSerde +spring.kafka.streams.application-id=angular-and-spring \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties new file mode 100644 index 00000000..732aa3ae --- /dev/null +++ b/backend/src/main/resources/application.properties @@ -0,0 +1,49 @@ +security.jwt.token.secret-key=${JWTTOKEN_SECRET:XQON8wjHynrlb7HyA5IKmjaBN3q2Vh2iPU6n6NIDaiRt6wzXjqwj_m9IHnh60zSCQnaC6Fut37aWBTqYpyFGKHLNCQdpyrTpMcGuUa_kcatWLm18VNJnFQrTdE1IrFXLevCVNLVSCLykujCnaZwPs9EWeraM3cFDx4NLCCDnTX7E46hO1paNHIyNFfNwr4T96fChjISJXCdxhJddp7dSt_aX7_JUdzJVDh7GhQY-RTDI2sboDWwujg_HUvnMt5huLFdy8c2Fm9RPjEj_nDKluLvbCCNipXCoAy8nGfB0C6DTuwPUK9PgrNe5ON5OKtJEY7rVj4n15InreksN5J0P0A==} +security.jwt.token.expire-length=86400000 +spring.data.mongodb.uri=mongodb://${MONGODB_HOST:localhost}:27017/test?compressors=zstd,snappy,zlib&connectTimeoutMS=3000&socketTimeoutMS=11000&wtimeoutMS=10000&serverSelectionTimeoutMS=5000&heartbeatFrequencyMS=5000&maxLifeTimeMS=25000 +spring.data.mongodb.auto-index-creation=true +spring.mongodb.embedded.version=6.0.4 +de.flapdoodle.mongodb.embedded.version=6.0.4 +spring.data.mongodb.connect-timeout=3000 +#spring.mongodb.embedded.version=4.0.12 +spring.main.allow-bean-definition-overriding=true +spring.lifecycle.timeout-per-shutdown-phase=${SHUTDOWN_PHASE:10s} +server.compression.enabled=true +server.http2.enabled=true +server.shutdown=graceful +server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml +server.compression.min-response-size=2KB +spring.codec.max-in-memory-size=2MB +server.servlet.session.timeout=10s +kubernetes.pod.cpu.constraint=${CPU_CONSTRAINT:false} +single.instance.slow-io=false +single.instance.deployment=false +management.health.livenessstate.enabled=true +management.health.readinessstate.enabled=true +management.health.mongo.enabled=true +management.health.enabled=true +management.endpoint.health.status.http-mapping.down=500 +management.endpoint.health.status.http-mapping.out_of_service=503 +management.endpoint.health.show-details=always +management.endpoints.web.exposure.include=health,prometheus,metrics +management.endpoints.jmx.exposure.include=health,prometheus,metrics +management.metrics.export.simple.enabled=true +management.metrics.export.prometheus.enabled=true +management.endpoint.prometheus.cache.time-to-live=100ms +management.endpoint.health.cache.time-to-live=100ms +management.metrics.export.simple.step=100ms +management.metrics.enabled=true +management.metrics.distribution.percentiles.http.server.requests=0.5,0.95,0.99 +management.metrics.distribution.percentiles.http.client.requests=0.5,0.95,0.99 +management.metrics.distribution.percentiles-histogram.http.server.requests=true +management.metrics.distribution.slo.http.server.requests=500ms +management.metrics.distribution.minimum-expected-value.http.server.requests=1ms +management.metrics.distribution.maximum-expected-value.http.server.requests=5s +management.metrics.enable.jvm=false +management.metrics.enable.logback=false +management.metrics.enable.process=false +management.metrics.enable.system=false +management.metrics.enable.mongodb=false +management.metrics.enable.jvm.gc.pause=true +management.metrics.enable.create=true +management.metrics.enable.http=true \ No newline at end of file diff --git a/backend/src/main/resources/currencyReport.jrxml b/backend/src/main/resources/currencyReport.jrxml new file mode 100644 index 00000000..61759d46 --- /dev/null +++ b/backend/src/main/resources/currencyReport.jrxml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <band splitType="Stretch"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/log4j.xml b/backend/src/main/resources/log4j.xml similarity index 100% rename from src/main/resources/log4j.xml rename to backend/src/main/resources/log4j.xml diff --git a/src/test/java/ch/xxx/trader/PasswordEncryptionTest.java b/backend/src/test/java/ch/xxx/trader/PasswordEncryptionTest.java similarity index 79% rename from src/test/java/ch/xxx/trader/PasswordEncryptionTest.java rename to backend/src/test/java/ch/xxx/trader/PasswordEncryptionTest.java index b5c12a4e..c227485b 100644 --- a/src/test/java/ch/xxx/trader/PasswordEncryptionTest.java +++ b/backend/src/test/java/ch/xxx/trader/PasswordEncryptionTest.java @@ -18,8 +18,10 @@ import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import ch.xxx.trader.domain.common.PasswordEncryption; @@ -30,21 +32,21 @@ public class PasswordEncryptionTest { public void abcEncryption() throws NoSuchAlgorithmException, InvalidKeySpecException { String salt = pe.generateSalt(); String encryptedPassword = pe.getEncryptedPassword("abc", salt); - Assert.assertTrue(pe.authenticate("abc", encryptedPassword, salt)); + Assertions.assertTrue(pe.authenticate("abc", encryptedPassword, salt)); } @Test public void strNumEncryption() throws NoSuchAlgorithmException, InvalidKeySpecException { String salt = pe.generateSalt(); String encryptedPassword = pe.getEncryptedPassword("abc123", salt); - Assert.assertTrue(pe.authenticate("abc123", encryptedPassword, salt)); + Assertions.assertTrue(pe.authenticate("abc123", encryptedPassword, salt)); } @Test public void realPwdEncryption() throws NoSuchAlgorithmException, InvalidKeySpecException { String salt = pe.generateSalt(); String encryptedPassword = pe.getEncryptedPassword("abc123%&/?!", salt); - Assert.assertTrue(pe.authenticate("abc123%&/?!", encryptedPassword, salt)); + Assertions.assertTrue(pe.authenticate("abc123%&/?!", encryptedPassword, salt)); } } diff --git a/backend/src/test/java/ch/xxx/trader/RandomNumberTest.java b/backend/src/test/java/ch/xxx/trader/RandomNumberTest.java new file mode 100644 index 00000000..5789a405 --- /dev/null +++ b/backend/src/test/java/ch/xxx/trader/RandomNumberTest.java @@ -0,0 +1,45 @@ +/** + * Copyright 2018 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader; + +import java.nio.ByteBuffer; +import java.util.Base64; +import java.util.Random; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class RandomNumberTest { + private Random rand = new Random(); + + @Test + public void generateRandomNumber() { + Assertions.assertNotNull(rand.nextLong()); + } + + // generates the bas64 encoded random number for the jwt signature + // property: security.jwt.token.secret-key + @Test + public void generateBase64RandomNumber() { + final int numberOfBytes = 32; + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES*numberOfBytes); + for(int i = 0;i resultOpt = this.clientMongoRepository.findOne(query, MyUser.class).blockOptional(); + Assertions.assertTrue(resultOpt.isPresent()); + Assertions.assertEquals(USERID, resultOpt.get().getUserId()); + } + + //@Test + @Order(3) + public void findMyUserNotFound() throws Exception { + Query query = new Query(); + query.addCriteria(Criteria.where("userId").is("XXX123")); + Optional resultOpt = this.clientMongoRepository.findOne(query, MyUser.class).blockOptional(); + Assertions.assertFalse(resultOpt.isPresent()); + } + + private MyUser createMyUser() { + MyUser myUser = new MyUser(); + myUser.setCreatedAt(new Date()); + myUser.setEmail("email"); + myUser.setPassword("password"); + myUser.setSalt("salt"); + myUser.setToken("token"); + myUser.setUserId(USERID); + return myUser; + } +} diff --git a/backend/src/test/java/ch/xxx/trader/architecture/MyArchitectureTests.java b/backend/src/test/java/ch/xxx/trader/architecture/MyArchitectureTests.java new file mode 100644 index 00000000..9f233c59 --- /dev/null +++ b/backend/src/test/java/ch/xxx/trader/architecture/MyArchitectureTests.java @@ -0,0 +1,148 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.architecture; + +import static com.tngtech.archunit.lang.conditions.ArchConditions.beAnnotatedWith; + +import java.util.List; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.web.bind.annotation.RestController; + +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests; +import com.tngtech.archunit.core.importer.Location; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.CompositeArchRule; +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import com.tngtech.archunit.library.Architectures; +import com.tngtech.archunit.library.GeneralCodingRules; +import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; + +import ch.xxx.trader.adapter.cron.TaskStarter; +import ch.xxx.trader.architecture.MyArchitectureTests.DoNotIncludeAotGenerated; +import ch.xxx.trader.architecture.MyArchitectureTests.DoNotIncludeNamedTests; +import jakarta.annotation.PostConstruct; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; + +@AnalyzeClasses(packages = "ch.xxx.trader", importOptions = { DoNotIncludeTests.class, DoNotIncludeAotGenerated.class, DoNotIncludeNamedTests.class }) +public class MyArchitectureTests { + private static final ArchRule NO_CLASSES_SHOULD_USE_FIELD_INJECTION = createNoFieldInjectionRule(); + + private JavaClasses importedClasses = new ClassFileImporter() + .withImportOptions(List.of(new DoNotIncludeTests(), new DoNotIncludeAotGenerated(), new DoNotIncludeNamedTests())) + .importPackages("ch.xxx.trader"); + + @ArchTest + static final ArchRule clean_architecture_respected = Architectures.onionArchitecture().domainModels("..domain..") + .applicationServices("..usecase..").adapter("rest", "..adapter.controller..") + .adapter("cron", "..adapter.cron..").adapter("repo", "..adapter.repository..") + .adapter("events", "..adapter.events..").adapter("config", "..adapter.config..") + .adapter("clients", "..adapter.clients..").withOptionalLayers(true); + + @ArchTest + static final ArchRule cyclesDomain = SlicesRuleDefinition.slices().matching("..domain.(*)..").should() + .beFreeOfCycles(); + + @ArchTest + static final ArchRule cyclesUseCases = SlicesRuleDefinition.slices().matching("..usecase.(*)..").should() + .beFreeOfCycles(); + + @ArchTest + static final ArchRule cyclesAdapter = SlicesRuleDefinition.slices().matching("..adapter.(*)..").should() + .beFreeOfCycles(); + + @Test + public void ruleControllerAnnotations() { + ArchRule beAnnotatedWith = ArchRuleDefinition.classes().that().resideInAPackage("..adapter.controller..") + .should().beAnnotatedWith(RestController.class); + beAnnotatedWith.check(this.importedClasses); + } + + @Test + public void ruleExceptionsType() { + ArchRule exceptionType = ArchRuleDefinition.classes().that().resideInAPackage("..domain.exceptions..").should() + .beAssignableTo(RuntimeException.class).orShould().beAssignableTo(DefaultErrorAttributes.class); + exceptionType.check(this.importedClasses); + } + + @Test + public void ruleCronJobMethodsAnnotations() { + ArchRule exceptionType = ArchRuleDefinition.methods().that().arePublic().and().areDeclaredInClassesThat() + .resideInAPackage("..adapter.cron..").and().areNotDeclaredIn(TaskStarter.class).should().beAnnotatedWith(PostConstruct.class).orShould() + .beAnnotatedWith(Scheduled.class).andShould().beAnnotatedWith(SchedulerLock.class); + exceptionType.check(this.importedClasses); + } + + @Test + public void ruleGeneralCodingRulesLoggers() { + ArchRuleDefinition.fields().that().haveRawType(Logger.class).should().bePrivate().andShould().beStatic() + .andShould().beFinal().because("we agreed on this convention").check(this.importedClasses); + } + + @Test + public void ruleGeneralCodingRules() { + ArchRule archRule = CompositeArchRule.of(GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS) + .and(NO_CLASSES_SHOULD_USE_FIELD_INJECTION).because("Good practice"); + JavaClasses classesToCheck = this.importedClasses + .that(JavaClass.Predicates.resideOutsideOfPackages("..adapter.clients.test..")); + archRule.check(classesToCheck); + } + + private static ArchRule createNoFieldInjectionRule() { + ArchCondition annotatedWithSpringAutowired = beAnnotatedWith( + "org.springframework.beans.factory.annotation.Autowired"); + ArchCondition annotatedWithGuiceInject = beAnnotatedWith("com.google.inject.Inject"); + ArchCondition annotatedWithJakartaInject = beAnnotatedWith("javax.inject.Inject"); + ArchRule beAnnotatedWithAnInjectionAnnotation = ArchRuleDefinition.noFields() + .should(annotatedWithSpringAutowired.or(annotatedWithGuiceInject).or(annotatedWithJakartaInject) + .as("be annotated with an injection annotation")); + return beAnnotatedWithAnInjectionAnnotation; + } + + + static final class DoNotIncludeNamedTests implements ImportOption { + private static final Pattern CUSTOM_TEST_PATTERN = Pattern.compile(".*(Test|Tests)\\.class$"); + + @Override + public boolean includes(Location location) { + return !location.matches(CUSTOM_TEST_PATTERN); + } + + } + static final class DoNotIncludeAotGenerated implements ImportOption { + private static final Pattern AOT_GENERATED_PATTERN = Pattern + .compile(".*(__BeanDefinitions|SpringCGLIB\\$\\$\\d)\\.class$"); + private static final Pattern AOT_TEST_GENERATED_PATTERN = Pattern + .compile(".*(__TestContext|__Autowiring).*\\.class$"); + + @Override + public boolean includes(Location location) { + return !(location.matches(AOT_GENERATED_PATTERN) || location.matches(AOT_TEST_GENERATED_PATTERN)); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/ch/xxx/trader/architecture/PatternTest.java b/backend/src/test/java/ch/xxx/trader/architecture/PatternTest.java new file mode 100644 index 00000000..50b97f0b --- /dev/null +++ b/backend/src/test/java/ch/xxx/trader/architecture/PatternTest.java @@ -0,0 +1,50 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.architecture; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.tngtech.archunit.core.importer.Location; + +import ch.xxx.trader.architecture.MyArchitectureTests.DoNotIncludeAotGenerated; + +public class PatternTest { + private List testStrings = List.of( + "ch.xxx.trader.adapter.controller.StatisticsController__BeanDefinitions.class", + "file:/home/sven/git/AngularAndSpring/backend/target/classes/ch/xxx/trader/TraderApplication$$SpringCGLIB$$0.class", + "ch.xxx.maps.usecase.service.CompanySiteService__TestContext001_BeanDefinitions.class"); + private final DoNotIncludeAotGenerated importOption = new MyArchitectureTests.DoNotIncludeAotGenerated(); + + @Test + public void testMatcherBeanDefinition() throws URISyntaxException { + Assertions.assertFalse(importOption.includes(Location.of(Path.of(testStrings.get(0))))); + } + + @Test + public void testMatcherCgLib() throws URISyntaxException { + Assertions.assertFalse(importOption.includes(Location.of(Path.of(testStrings.get(1))))); + } + + @Test + public void testMatcherTests() throws URISyntaxException { + Assertions.assertFalse(importOption.includes(Location.of(Path.of(testStrings.get(2))))); + } +} diff --git a/backend/src/test/java/ch/xxx/trader/usecase/services/MyUserServiceTest.java b/backend/src/test/java/ch/xxx/trader/usecase/services/MyUserServiceTest.java new file mode 100644 index 00000000..472a98bb --- /dev/null +++ b/backend/src/test/java/ch/xxx/trader/usecase/services/MyUserServiceTest.java @@ -0,0 +1,79 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isA; + +import java.util.Optional; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.security.crypto.password.PasswordEncoder; + +import ch.xxx.trader.domain.common.PasswordEncryption; +import ch.xxx.trader.domain.model.dto.RefreshTokenDto; +import ch.xxx.trader.domain.model.entity.MyMongoRepository; +import ch.xxx.trader.domain.model.entity.MyUser; +import reactor.core.publisher.Mono; + +@ExtendWith(MockitoExtension.class) +public class MyUserServiceTest { + @Mock + private JwtTokenService jwtTokenService; + @Mock + private PasswordEncryption passwordEncryption; + @Mock + private MyMongoRepository myMongoRepository; + @Mock + private PasswordEncoder passwordEncoder; + @InjectMocks + private MyUserServiceDb myUserService; + + @SuppressWarnings("unchecked") + @Test + public void postUserSigninTest() throws Exception { + MyUser myUser = new MyUser(); + myUser.setUserId("XXX"); + myUser.setPassword("ABCDEFG123"); + Mockito.when(this.myMongoRepository.findOne(isA(Query.class), isA(Class.class))).thenReturn(Mono.just(myUser)); + Mockito.when(this.myMongoRepository.save(any(MyUser.class))).thenReturn(Mono.just(myUser)); + Optional result = this.myUserService.postUserSignin(myUser).blockOptional(); + Assertions.assertTrue(result.isPresent()); + } + + @Test + public void refreshTokenTestSuccess() throws Exception { + final String DUMMY_TOKEN = "ABC"; + Mockito.when(this.jwtTokenService.resolveToken(any(String.class))).thenReturn(Optional.of(DUMMY_TOKEN)); + Mockito.when(this.jwtTokenService.refreshToken(any(String.class))).thenReturn(DUMMY_TOKEN); + Mono result = this.myUserService.refreshToken(DUMMY_TOKEN); + Assertions.assertNotNull(result); + Assertions.assertEquals(DUMMY_TOKEN, result.block().getRefreshToken()); + } + + @Test + public void refreshTokenTestFailed() throws Exception { + final String DUMMY_TOKEN = "ABC"; + Mockito.when(this.jwtTokenService.resolveToken(any(String.class))).thenReturn(Optional.empty()); + Assertions.assertThrows(Exception.class, () -> this.myUserService.refreshToken(DUMMY_TOKEN)); + } +} diff --git a/backend/src/test/java/ch/xxx/trader/usecase/services/StatisticServiceTest.java b/backend/src/test/java/ch/xxx/trader/usecase/services/StatisticServiceTest.java new file mode 100644 index 00000000..de982387 --- /dev/null +++ b/backend/src/test/java/ch/xxx/trader/usecase/services/StatisticServiceTest.java @@ -0,0 +1,241 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package ch.xxx.trader.usecase.services; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import ch.xxx.trader.domain.model.dto.CommonStatisticsDto; +import ch.xxx.trader.domain.model.dto.RangeDto; +import ch.xxx.trader.domain.model.entity.QuoteBf; +import ch.xxx.trader.domain.model.entity.QuoteBs; + +@ExtendWith(MockitoExtension.class) +public class StatisticServiceTest { + private enum StatisticKeys { + getPerformance, getAvgVolume, getRange, getVolatility + } + +// @Mock +// private MyMongoRepository myMongoRepository; + + + @Test + public void statistic5Years() { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBs = createBsQuotes(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics5Years(quotesBs, dto); + Assertions.assertEquals(dto.getPerformance5Year().longValue(), 800L); + Assertions.assertEquals(dto.getAvgVolume5Year(), BigDecimal.valueOf(50L)); + Assertions.assertEquals(dto.getRange5Year().getMin(), BigDecimal.TEN); + Assertions.assertEquals(dto.getRange5Year().getMax(), BigDecimal.valueOf(90L)); + Assertions.assertEquals(dto.getVolatility5Year(), new BigDecimal("25.81988897471611256786176933188266")); + } + + @Test + public void statistic2Years() { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = createBfQuotes(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics2Years(quotesBf, dto); + Assertions.assertEquals(dto.getPerformance2Year().longValue(), 350L); + Assertions.assertEquals(dto.getAvgVolume2Year(), BigDecimal.valueOf(55L)); + Assertions.assertEquals(dto.getRange2Year().getMin(), BigDecimal.valueOf(20L)); + Assertions.assertEquals(dto.getRange2Year().getMax(), BigDecimal.valueOf(90L)); + Assertions.assertEquals(dto.getVolatility2Year(), new BigDecimal("22.91287847477920003294023596864004")); + } + + @Test + public void statistic1Year() { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBs = createBsQuotes(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics1Year(quotesBs, dto); + Assertions.assertEquals(dto.getPerformance1Year().longValue(), 200L); + Assertions.assertEquals(dto.getAvgVolume1Year(), BigDecimal.valueOf(60L)); + Assertions.assertEquals(dto.getRange1Year().getMin(), BigDecimal.valueOf(30L)); + Assertions.assertEquals(dto.getRange1Year().getMax(), BigDecimal.valueOf(90L)); + Assertions.assertEquals(dto.getVolatility1Year(), new BigDecimal("20")); + } + + @Test + public void statistic6Months() { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = createBfQuotes(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics6Months(quotesBf, dto); + Assertions.assertEquals(dto.getPerformance6Month().longValue(), 125L); + Assertions.assertEquals(dto.getAvgVolume6Month(), BigDecimal.valueOf(65L)); + Assertions.assertEquals(dto.getRange6Month().getMin(), BigDecimal.valueOf(40L)); + Assertions.assertEquals(dto.getRange6Month().getMax(), BigDecimal.valueOf(90L)); + Assertions.assertEquals(dto.getVolatility6Month(), new BigDecimal("17.07825127659933063870173113420175")); + } + + @Test + public void statistic3Months() { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBs = createBsQuotes(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics3Months(quotesBs, dto); + Assertions.assertEquals(dto.getPerformance3Month().longValue(), 80L); + Assertions.assertEquals(dto.getAvgVolume3Month(), BigDecimal.valueOf(70L)); + Assertions.assertEquals(dto.getRange3Month().getMin(), BigDecimal.valueOf(50L)); + Assertions.assertEquals(dto.getRange3Month().getMax(), BigDecimal.valueOf(90L)); + Assertions.assertEquals(dto.getVolatility3Month(), new BigDecimal("14.14213562373095048801688724209698")); + } + + @Test + public void statistic1Month() { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = createBfQuotes(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics1Month(quotesBf, dto); + Assertions.assertEquals(dto.getPerformance1Month().longValue(), 50L); + Assertions.assertEquals(dto.getAvgVolume1Month(), BigDecimal.valueOf(75L)); + Assertions.assertEquals(dto.getRange1Month().getMin(), BigDecimal.valueOf(60L)); + Assertions.assertEquals(dto.getRange1Month().getMax(), BigDecimal.valueOf(90L)); + Assertions.assertEquals(dto.getVolatility1Month(), new BigDecimal("11.18033988749894848204586834365638")); + } + + @Test + public void statistic1MonthEmpty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = List.of(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics1Month(quotesBf, dto); + String durationStr = "1Month"; + + checkEmptyResult(dto, durationStr); + } + + @Test + public void statistic3MonthEmpty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = List.of(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics3Months(quotesBf, dto); + String durationStr = "3Month"; + + checkEmptyResult(dto, durationStr); + } + + @Test + public void statistic6MonthEmpty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = List.of(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics6Months(quotesBf, dto); + String durationStr = "6Month"; + + checkEmptyResult(dto, durationStr); + } + + @Test + public void statistic1YearEmpty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = List.of(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics1Year(quotesBf, dto); + String durationStr = "1Year"; + + checkEmptyResult(dto, durationStr); + } + + @Test + public void statistic2YearEmpty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = List.of(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics2Years(quotesBf, dto); + String durationStr = "2Year"; + + checkEmptyResult(dto, durationStr); + } + + @Test + public void statistic5YearEmpty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { +// StatisticService statisticService = new StatisticService(this.myMongoRepository); + List quotesBf = List.of(); + CommonStatisticsDto dto = new CommonStatisticsDto(); + StatisticService.calcStatistics5Years(quotesBf, dto); + String durationStr = "5Year"; + + checkEmptyResult(dto, durationStr); + } + + private void checkEmptyResult(CommonStatisticsDto dto, String durationStr) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Method declaredMethod = CommonStatisticsDto.class.getDeclaredMethod(StatisticKeys.getPerformance + durationStr); + Assertions.assertEquals(((Double) declaredMethod.invoke(dto)).longValue(), 0L); + declaredMethod = CommonStatisticsDto.class.getDeclaredMethod(StatisticKeys.getAvgVolume + durationStr); + Assertions.assertEquals(((BigDecimal) declaredMethod.invoke(dto)).longValue(), 0L); + declaredMethod = CommonStatisticsDto.class.getDeclaredMethod(StatisticKeys.getRange + durationStr); + Assertions.assertEquals(((RangeDto) declaredMethod.invoke(dto)).getMin().longValue(), 0L); + declaredMethod = CommonStatisticsDto.class.getDeclaredMethod(StatisticKeys.getRange + durationStr); + Assertions.assertEquals(((RangeDto) declaredMethod.invoke(dto)).getMax().longValue(), 0L); + declaredMethod = CommonStatisticsDto.class.getDeclaredMethod(StatisticKeys.getVolatility + durationStr); + Assertions.assertEquals(((BigDecimal) declaredMethod.invoke(dto)).longValue(), 0L); + } + + private List createBfQuotes() { + return List.of( + this.createBfQuote(BigDecimal.TEN, BigDecimal.TEN, LocalDate.now().minusYears(4L)), + this.createBfQuote(BigDecimal.valueOf(20L), BigDecimal.valueOf(20L), LocalDate.now().minusMonths(15L)), + this.createBfQuote(BigDecimal.valueOf(30L), BigDecimal.valueOf(30L), LocalDate.now().minusMonths(11L)), + this.createBfQuote(BigDecimal.valueOf(40L), BigDecimal.valueOf(40L), LocalDate.now().minusMonths(5L)), + this.createBfQuote(BigDecimal.valueOf(50L), BigDecimal.valueOf(50L), LocalDate.now().minusMonths(2L)), + this.createBfQuote(BigDecimal.valueOf(60L), BigDecimal.valueOf(60L) ,LocalDate.now().minusDays(10L)), + this.createBfQuote(BigDecimal.valueOf(70L), BigDecimal.valueOf(70L), LocalDate.now().minusDays(10L)), + this.createBfQuote(BigDecimal.valueOf(80L), BigDecimal.valueOf(80L), LocalDate.now().minusDays(10L)), + this.createBfQuote(BigDecimal.valueOf(90L), BigDecimal.valueOf(90L), LocalDate.now().minusDays(10L))); + } + + private List createBsQuotes() { + return List.of( + this.createBsQuote(BigDecimal.TEN, BigDecimal.TEN, LocalDate.now().minusYears(4L)), + this.createBsQuote(BigDecimal.valueOf(20L), BigDecimal.valueOf(20L), LocalDate.now().minusMonths(15L)), + this.createBsQuote(BigDecimal.valueOf(30L), BigDecimal.valueOf(30L), LocalDate.now().minusMonths(11L)), + this.createBsQuote(BigDecimal.valueOf(40L), BigDecimal.valueOf(40L), LocalDate.now().minusMonths(5L)), + this.createBsQuote(BigDecimal.valueOf(50L), BigDecimal.valueOf(50L), LocalDate.now().minusMonths(2L)), + this.createBsQuote(BigDecimal.valueOf(60L), BigDecimal.valueOf(60L) ,LocalDate.now().minusDays(10L)), + this.createBsQuote(BigDecimal.valueOf(70L), BigDecimal.valueOf(70L), LocalDate.now().minusDays(10L)), + this.createBsQuote(BigDecimal.valueOf(80L), BigDecimal.valueOf(80L), LocalDate.now().minusDays(10L)), + this.createBsQuote(BigDecimal.valueOf(90L), BigDecimal.valueOf(90L), LocalDate.now().minusDays(10L))); + } + + private QuoteBs createBsQuote(BigDecimal last, BigDecimal volume, LocalDate localDate) { + QuoteBs quoteBs = new QuoteBs(null, last, null, null, null, volume, null, null, null); + quoteBs.setCreatedAt(Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + return quoteBs; + } + + private QuoteBf createBfQuote(BigDecimal last, BigDecimal volume, LocalDate localDate) { + QuoteBf quoteBf = new QuoteBf(null, null, null, last, null, null, volume, null); + quoteBf.setCreatedAt(Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + return quoteBf; + } +} diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties new file mode 100644 index 00000000..a972fb54 --- /dev/null +++ b/backend/src/test/resources/application.properties @@ -0,0 +1,2 @@ +spring.data.mongodb.uri=mongodb://${MONGODB_HOST:localhost}:27017/test?connectTimeoutMS=3000&socketTimeoutMS=11000&wtimeoutMS=10000&serverSelectionTimeoutMS=5000&heartbeatFrequencyMS=5000&maxLifeTimeMS=25000 +kubernetes.pod.cpu.constraint=false \ No newline at end of file diff --git a/buildDocker.sh b/buildDocker.sh index dff0f1ed..4e14bff8 100644 --- a/buildDocker.sh +++ b/buildDocker.sh @@ -1,3 +1,5 @@ #!/bin/sh -docker build -t angular2guy/trader:latest . -docker run -p 8080:8080 --network="host" angular2guy/trader:latest \ No newline at end of file +#./mvnw clean install -Ddocker=true -Dnpm.test.script=test-chromium +./mvnw clean install -Ddocker=true +docker build -t angular2guy/angularandspring:latest --build-arg JAR_FILE=angularandspring-backend-0.0.1-SNAPSHOT.jar --no-cache . +docker run -p 8080:8080 --memory="512m" --cpus=1.0 --network="host" angular2guy/angularandspring:latest \ No newline at end of file diff --git a/frontend/.classpath b/frontend/.classpath new file mode 100644 index 00000000..0ec47b64 --- /dev/null +++ b/frontend/.classpath @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/.project b/frontend/.project new file mode 100644 index 00000000..d51ff471 --- /dev/null +++ b/frontend/.project @@ -0,0 +1,23 @@ + + + angularandspring-frontend + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/frontend/.settings/org.eclipse.core.resources.prefs b/frontend/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..29abf999 --- /dev/null +++ b/frontend/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/frontend/.settings/org.eclipse.jdt.core.prefs b/frontend/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..9529cacf --- /dev/null +++ b/frontend/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=24 +org.eclipse.jdt.core.compiler.compliance=24 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=24 diff --git a/frontend/.settings/org.eclipse.m2e.core.prefs b/frontend/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/frontend/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/frontend/pom.xml b/frontend/pom.xml new file mode 100644 index 00000000..ab8d98ab --- /dev/null +++ b/frontend/pom.xml @@ -0,0 +1,129 @@ + + + + 4.0.0 + + angularandspring-frontend + + + ch.xxx + angularandspring + 0.0.1-SNAPSHOT + + + + + + maven-clean-plugin + + false + + + src/angular/dist + false + + + src/angular/node_modules + false + + + + + + exec-maven-plugin + org.codehaus.mojo + 3.0.0 + + + npm install + + exec + + generate-sources + + npm + + install + + src/angular + + + + + + + angular-cli build + + exec + + generate-resources + + npm + + run + build + + src/angular + + + + angular-cli test + + exec + + test + + npm + + run + ${npm.test.script} + + src/angular + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + + lifecycle-mapping + org.eclipse.m2e + 1.0.0 + + + + + + exec-maven-plugin + org.codehaus.mojo + [3.0.0,) + + exec + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/angular/trader/.editorconfig b/frontend/src/angular/.editorconfig similarity index 100% rename from src/angular/trader/.editorconfig rename to frontend/src/angular/.editorconfig diff --git a/frontend/src/angular/.eslintrc.json b/frontend/src/angular/.eslintrc.json new file mode 100644 index 00000000..94e9cee5 --- /dev/null +++ b/frontend/src/angular/.eslintrc.json @@ -0,0 +1,63 @@ +{ + "root": true, + "ignorePatterns": [ + "projects/**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "tsconfig.json", + "e2e/tsconfig.json" + ], + "createDefaultProgram": true + }, + "extends": [ + "plugin:@angular-eslint/ng-cli-compat", + "plugin:@angular-eslint/ng-cli-compat--formatting-add-on", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "app", + "style": "kebab-case" + } + ], + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "app", + "style": "camelCase" + } + ], + "@typescript-eslint/explicit-member-accessibility": [ + "off", + { + "accessibility": "explicit" + } + ], + "arrow-parens": [ + "off", + "always" + ], + "import/order": "off" + } + }, + { + "files": [ + "*.html" + ], + "extends": [ + "plugin:@angular-eslint/template/recommended" + ], + "rules": {} + } + ] +} diff --git a/src/angular/trader/.gitignore b/frontend/src/angular/.gitignore similarity index 95% rename from src/angular/trader/.gitignore rename to frontend/src/angular/.gitignore index 54bfd200..3269679c 100644 --- a/src/angular/trader/.gitignore +++ b/frontend/src/angular/.gitignore @@ -25,6 +25,7 @@ !.vscode/extensions.json # misc +/.angular/cache /.sass-cache /connect.lock /coverage @@ -40,3 +41,4 @@ testem.log # System Files .DS_Store Thumbs.db +/.angular/ diff --git a/src/angular/trader/README.md b/frontend/src/angular/README.md similarity index 100% rename from src/angular/trader/README.md rename to frontend/src/angular/README.md diff --git a/frontend/src/angular/angular.json b/frontend/src/angular/angular.json new file mode 100644 index 00000000..c1cfcd7f --- /dev/null +++ b/frontend/src/angular/angular.json @@ -0,0 +1,190 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "testproject": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "i18n": { + "locales": { + "de": "i18n/messages.de.json" + }, + "sourceLocale": "en" + }, + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": { + "base": "dist/testproject" + }, + "index": "src/index.html", + "polyfills": [ + "src/polyfills.ts", + "@angular/localize/init" + ], + "tsConfig": "tsconfig.app.json", + "preserveSymlinks": true, + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", + "src/styles.scss" + ], + "scripts": [], + "aot": false, + "extractLicenses": false, + "sourceMap": true, + "optimization": false, + "namedChunks": true, + "browser": "src/main.ts" + }, + "configurations": { + "de": { + "localize": [ + "de" + ] + }, + "en": { + "localize": [ + "en" + ] + }, + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "localize": true, + "aot": true, + "extractLicenses": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "testproject:build" + }, + "configurations": { + "production": { + "buildTarget": "testproject:build:production" + }, + "de": { + "buildTarget": "testproject:build:de" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "testproject:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "styles": [ + "src/styles.scss" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } + } + } + }, + "testproject-e2e": { + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "testproject:serve" + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } + } + } + } + }, + "cli": { + "analytics": false + }, + "schematics": { + "@schematics/angular:component": { + "type": "component" + }, + "@schematics/angular:directive": { + "type": "directive" + }, + "@schematics/angular:service": { + "type": "service" + }, + "@schematics/angular:guard": { + "typeSeparator": "." + }, + "@schematics/angular:interceptor": { + "typeSeparator": "." + }, + "@schematics/angular:module": { + "typeSeparator": "." + }, + "@schematics/angular:pipe": { + "typeSeparator": "." + }, + "@schematics/angular:resolver": { + "typeSeparator": "." + } + } +} diff --git a/src/main/resources/static/index.html b/frontend/src/angular/base/index.html similarity index 100% rename from src/main/resources/static/index.html rename to frontend/src/angular/base/index.html diff --git a/frontend/src/angular/browserslist b/frontend/src/angular/browserslist new file mode 100644 index 00000000..05f5b7ac --- /dev/null +++ b/frontend/src/angular/browserslist @@ -0,0 +1,7 @@ +last 2 Chrome version +last 2 Firefox version +last 2 Edge major versions +last 2 Safari major version +last 2 iOS major versions +Firefox ESR +not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the `not` prefix on this line..<% } %> \ No newline at end of file diff --git a/src/angular/trader/e2e/app.e2e-spec.ts b/frontend/src/angular/e2e/src/app.e2e-spec.ts similarity index 100% rename from src/angular/trader/e2e/app.e2e-spec.ts rename to frontend/src/angular/e2e/src/app.e2e-spec.ts diff --git a/src/angular/trader/e2e/app.po.ts b/frontend/src/angular/e2e/src/app.po.ts similarity index 100% rename from src/angular/trader/e2e/app.po.ts rename to frontend/src/angular/e2e/src/app.po.ts diff --git a/src/angular/trader/e2e/tsconfig.e2e.json b/frontend/src/angular/e2e/tsconfig.e2e.json similarity index 100% rename from src/angular/trader/e2e/tsconfig.e2e.json rename to frontend/src/angular/e2e/tsconfig.e2e.json diff --git a/frontend/src/angular/i18n/messages.de.json b/frontend/src/angular/i18n/messages.de.json new file mode 100644 index 00000000..0ac3ac49 --- /dev/null +++ b/frontend/src/angular/i18n/messages.de.json @@ -0,0 +1,76 @@ +{ + "locale": "de", + "translations": { + "bitfinex": "Bitfinex", + "back": "Back", + "last": "Last:", + "high": "High:", + "low": "Low:", + "bid": "Bid:", + "ask": "Ask:", + "mid": "Mid:", + "pair": "Pair:", + "timestamp": "Timestamp:", + "volume": "Volume:", + "radioDays": "{VAR_PLURAL, plural, =1 {today} other {{INTERPOLATION} days}}", + "linReg": "add Linear Reg", + "showReport": "show report", + "bitstamp": "Bitstamp", + "open": "Open:", + "vwap": "Vwap:", + "coinbase": "Coinbase", + "lastUsd": "Last Usd:", + "lastEur": "Last Eur:", + "lastYen": "Last Yen:", + "lastGbp": "Last Pound:", + "itbit": "Itbit", + "orderbooks": "Orderbooks", + "buy": "Buy", + "sell": "Sell", + "search": "Search", + "orderbooksBitstampOrders": "Bitstamp Orders", + "price": "Price", + "amount": "Amount", + "orderbooksItbitOrders": "Itbit Orders", + "orderbooksBitfinexOrders": "Bitfinex Orders", + "loginLogin": "Login", + "loginUsername": "Username", + "loginPassword": "Password", + "loginLoginFailed": " Login Failed ", + "ok": "Ok", + "cancel": "Cancel", + "loginSignin": "Signin", + "loginEmail": "Email", + "loginSigninFailed": " Signin Failed ", + "loginWaiting": "Waiting...", + "quoteOverviewCurrencyTable": "Curreny Table", + "quoteOverviewStatistics": "Statistics", + "quoteOverviewOrderbooks": "Orderbooks", + "quoteOverviewLogin": "Login", + "quoteOverviewLogout": "Logout", + "quoteOverviewExchange": " Exchange ", + "quoteOverviewCurrencyPair": " Currency Pair ", + "quoteOverviewLast": " Last ", + "quoteOverviewHigh": "High", + "quoteOverviewLow": "Low", + "quoteOverviewVolume": " Volume ", + "splashWelcome": "Willkommen bei AngularAndSpring", + "bitcoin": "Bitcoin", + "ether": "Ether", + "litecoin": "Litecoin", + "ripple": "Ripple", + "statisticsDuration": "Duration", + "statisticsPerformance": "Performance", + "statisticsVolatility": "Volatility", + "statisticsAvgVolume": "Avg. Volume", + "statisticsRangeMin": "Range Min", + "statisticsRangeMax": "Range Max", + "Month1": "1 Month", + "Month3": "3 Months", + "Month6": "6 Months", + "Year1": "1 Year", + "Year2": "2 Years", + "Year5": "5 Years", + "statistics": "Statistics" + } +} \ No newline at end of file diff --git a/frontend/src/angular/karma.conf.js b/frontend/src/angular/karma.conf.js new file mode 100644 index 00000000..485f5927 --- /dev/null +++ b/frontend/src/angular/karma.conf.js @@ -0,0 +1,49 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma'), + require('karma-junit-reporter') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage'), + reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, +// reporters: ['progress', 'kjhtml', 'junit'], + reporters: ['progress', 'kjhtml'], + junitReporter: { + outputDir: 'reports', // results will be saved as $outputDir/$browserName.xml + outputFile: 'junit.xml', // if included, results will be saved as $outputDir/$browserName/$outputFile + useBrowserName: false // add browser name to report and classes names + }, + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chromium', 'ChromeHeadless', 'ChromiumHeadless'], + customLaunchers: { + ChromeHeadless: { + base: 'Chrome', + flags: ['--no-sandbox','--headless', '--disable-gpu', '--remote-debugging-port=9222'] + }, + ChromiumHeadless: { + base: 'Chromium', + flags: ['--no-sandbox','--headless', '--disable-gpu', '--remote-debugging-port=9222'] + } + }, + singleRun: false, + restartOnFileChange: true + }); +}; \ No newline at end of file diff --git a/frontend/src/angular/package-lock.json b/frontend/src/angular/package-lock.json new file mode 100644 index 00000000..76963d50 --- /dev/null +++ b/frontend/src/angular/package-lock.json @@ -0,0 +1,18543 @@ +{ + "name": "trader", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "trader", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@angular/animations": "^20.0.0", + "@angular/cdk": "^20.0.1", + "@angular/common": "^20.0.0", + "@angular/compiler": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/forms": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/material": "^20.0.1", + "@angular/material-luxon-adapter": "^20.0.1", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-browser-dynamic": "^20.0.0", + "@angular/router": "^20.0.0", + "@types/luxon": "^2.0.0", + "luxon": "^3.2.1", + "material-icons": "^1.11.11", + "ngx-simple-charts": "^20.0.0", + "rxjs": "^7.4.0", + "tslib": "^2.3.1", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^20.0.0", + "@angular-eslint/builder": "^20.0.0-alpha.1", + "@angular-eslint/eslint-plugin": "^20.0.0-alpha.1", + "@angular-eslint/eslint-plugin-template": "^20.0.0-alpha.1", + "@angular-eslint/schematics": "^20.0.0-alpha.1", + "@angular-eslint/template-parser": "^20.0.0-alpha.1", + "@angular/cli": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@angular/language-service": "^20.0.0", + "@types/jasmine": "~3.10.0", + "@types/jasminewd2": "~2.0.3", + "@types/node": "^18.0.0", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", + "cpy-cli": "^4.2.0", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jasmine-core": "~4.0.0", + "jasmine-spec-reporter": "~7.0.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.1.0", + "karma-jasmine": "~4.0.0", + "karma-jasmine-html-reporter": "^1.7.0", + "karma-junit-reporter": "~2.0.0", + "mkdirp": "^1.0.4", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.0", + "rimraf": "^5.0.2", + "ts-node": "~10.4.0", + "typescript": "~5.8.3", + "webpack-bundle-analyzer": "^4.5.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.2000.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2000.0.tgz", + "integrity": "sha512-6accOuvf1BY6hTO5LzYcxp2Dpl0bThgYF3KdwVWqrYF5+6PWfQLdy+rKxBiCIv0+0OngZVI79RuAtUKFowFM/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.0.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.0.0.tgz", + "integrity": "sha512-6JAVLjGLSTy69FAXTPzi9t4SswT4b3mOiz8GPleNTO0VmxgQA8C+zUqG81fH1ZDdSZBfUZcbgim+Y47G3cORcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2000.0", + "@angular-devkit/build-webpack": "0.2000.0", + "@angular-devkit/core": "20.0.0", + "@angular/build": "20.0.0", + "@babel/core": "7.27.1", + "@babel/generator": "7.27.1", + "@babel/helper-annotate-as-pure": "7.27.1", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.27.1", + "@babel/plugin-transform-async-to-generator": "7.27.1", + "@babel/plugin-transform-runtime": "7.27.1", + "@babel/preset-env": "7.27.2", + "@babel/runtime": "7.27.1", + "@discoveryjs/json-ext": "0.6.3", + "@ngtools/webpack": "20.0.0", + "@vitejs/plugin-basic-ssl": "2.0.0", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.21", + "babel-loader": "10.0.0", + "browserslist": "^4.21.5", + "copy-webpack-plugin": "13.0.0", + "css-loader": "7.1.2", + "esbuild-wasm": "0.25.5", + "fast-glob": "3.3.3", + "http-proxy-middleware": "3.0.5", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "karma-source-map-support": "1.4.0", + "less": "4.3.0", + "less-loader": "12.3.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.3.1", + "mini-css-extract-plugin": "2.9.2", + "open": "10.1.2", + "ora": "8.2.0", + "picomatch": "4.0.2", + "piscina": "5.0.0", + "postcss": "8.5.3", + "postcss-loader": "8.1.1", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.2", + "sass": "1.88.0", + "sass-loader": "16.0.5", + "semver": "7.7.2", + "source-map-loader": "5.0.0", + "source-map-support": "0.5.21", + "terser": "5.39.1", + "tree-kill": "1.2.2", + "tslib": "2.8.1", + "webpack": "5.99.8", + "webpack-dev-middleware": "7.4.2", + "webpack-dev-server": "5.2.1", + "webpack-merge": "6.0.1", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.25.5" + }, + "peerDependencies": { + "@angular/compiler-cli": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/service-worker": "^20.0.0", + "@angular/ssr": "^20.0.0", + "@web/test-runner": "^0.20.0", + "browser-sync": "^3.0.2", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^20.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.8 <5.9" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular/build": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.0.0.tgz", + "integrity": "sha512-b/FAvvUbsMEgr+UlvTtDz4NCv+BFi+55swtKRmaritvZ2rDfhF1x9tUmSkT6GebGXkI/Gg0kl5rJoD5iv5lY3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2000.0", + "@babel/core": "7.27.1", + "@babel/helper-annotate-as-pure": "7.27.1", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "5.1.10", + "@vitejs/plugin-basic-ssl": "2.0.0", + "beasties": "0.3.4", + "browserslist": "^4.23.0", + "esbuild": "0.25.5", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "listr2": "8.3.3", + "magic-string": "0.30.17", + "mrmime": "2.0.1", + "parse5-html-rewriting-stream": "7.1.0", + "picomatch": "4.0.2", + "piscina": "5.0.0", + "rollup": "4.40.2", + "sass": "1.88.0", + "semver": "7.7.2", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.13", + "vite": "6.3.5", + "watchpack": "2.4.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.3.0" + }, + "peerDependencies": { + "@angular/compiler": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/service-worker": "^20.0.0", + "@angular/ssr": "^20.0.0", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "^20.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.8 <5.9", + "vitest": "^3.1.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@inquirer/confirm": { + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.10.tgz", + "integrity": "sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.11", + "@inquirer/type": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz", + "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz", + "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz", + "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz", + "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz", + "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz", + "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz", + "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz", + "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz", + "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz", + "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz", + "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz", + "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz", + "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz", + "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz", + "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", + "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz", + "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz", + "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz", + "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz", + "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular-devkit/build-angular/node_modules/rollup": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", + "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.2", + "@rollup/rollup-android-arm64": "4.40.2", + "@rollup/rollup-darwin-arm64": "4.40.2", + "@rollup/rollup-darwin-x64": "4.40.2", + "@rollup/rollup-freebsd-arm64": "4.40.2", + "@rollup/rollup-freebsd-x64": "4.40.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", + "@rollup/rollup-linux-arm-musleabihf": "4.40.2", + "@rollup/rollup-linux-arm64-gnu": "4.40.2", + "@rollup/rollup-linux-arm64-musl": "4.40.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-musl": "4.40.2", + "@rollup/rollup-linux-s390x-gnu": "4.40.2", + "@rollup/rollup-linux-x64-gnu": "4.40.2", + "@rollup/rollup-linux-x64-musl": "4.40.2", + "@rollup/rollup-win32-arm64-msvc": "4.40.2", + "@rollup/rollup-win32-ia32-msvc": "4.40.2", + "@rollup/rollup-win32-x64-msvc": "4.40.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.2000.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2000.0.tgz", + "integrity": "sha512-bIbz6uFQLTBvmadWJo/KEF1GruqIC23HF8YcUfy/1AuSd07EjoWL8wZrpl6eY+RE8hjua3AC1XSrzWD2e+xd8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2000.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/core": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.0.0.tgz", + "integrity": "sha512-cnB/I1QQC3WoIcb+f/7hknOOkgIFjAuxd7nW1RnS+pn0qQTWyjnXjq2jocx2TBMwZRikycc7f3mlA1DgWzJUuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.2", + "source-map": "0.7.4" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.0.0.tgz", + "integrity": "sha512-35WbWP8ARnaqVjOzy7IOyWsY/jeyUqfVj4KgHG2O4fHAhIhaBqhP8dDDP+SwM+bToIqklg0fzHUUhFTRxzzyoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.0.0", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "8.2.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-eslint/builder": { + "version": "20.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-20.0.0-alpha.1.tgz", + "integrity": "sha512-ndQiMcwVXOkYn5wTr+g+krZ8JDREEYgS543AgCBBCXsOurOrN9o4wVai1r3mIaw5LdAoG8jVdcADVN3j1G5OqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": ">= 0.2000.0 < 0.2100.0", + "@angular-devkit/core": ">= 20.0.0 < 21.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "20.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-20.0.0-alpha.1.tgz", + "integrity": "sha512-BWri7fn+O2upEX8USB0OXLt0muQgqasv5A9AQCjc9AmZq57q3EPkIFc5IeUzHFd+4McMkZVlbezbB/3+ca64PQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "20.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-20.0.0-alpha.1.tgz", + "integrity": "sha512-81ZR+1TXK4+9mxbjsiyppdlayndQxDgSor4fmaVcDNi/8uwXKSwyTFWpD+M9683JSmEs4TG2xsgrahRve1zPWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.0.0-alpha.1", + "@angular-eslint/utils": "20.0.0-alpha.1" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "20.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-20.0.0-alpha.1.tgz", + "integrity": "sha512-w4K4NDFhLyf4KTTUNSwhOiNrSLPLbFdI00aTgjU7Krjlx46EiuvGZsVQbTirYDMl8F/Yi3y1FU0bc0PgQPQtOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.0.0-alpha.1", + "@angular-eslint/utils": "20.0.0-alpha.1", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "20.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-20.0.0-alpha.1.tgz", + "integrity": "sha512-p4vvIEnMba5bte4VT0fNWX3koOwxz5DJi8Qv+g4y77vK+ymKRXvBwlXUuRixVRDxjPxtmbUR1ixBwPN/AhrWLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": ">= 20.0.0 < 21.0.0", + "@angular-devkit/schematics": ">= 20.0.0 < 21.0.0", + "@angular-eslint/eslint-plugin": "20.0.0-alpha.1", + "@angular-eslint/eslint-plugin-template": "20.0.0-alpha.1", + "ignore": "7.0.4", + "semver": "7.7.2", + "strip-json-comments": "3.1.1" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "20.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-20.0.0-alpha.1.tgz", + "integrity": "sha512-iJ625T38amHkFczm0Q5hdW/yHPf1gHto9NqO5xjX13y01TeZjQPlDkuBipq36zNf4H9Vrnfvx2UNGoQPVAR9tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.0.0-alpha.1", + "eslint-scope": "^8.0.2" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "20.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-20.0.0-alpha.1.tgz", + "integrity": "sha512-fRUxoJMXG/MoFdTMb5MbP9z9Dw2BahaCUotc3Cjj/fUcFVTf8kxdMEg74iGFbIfxkn3IOwF3pjLEtt3VQwgCYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "20.0.0-alpha.1" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/animations": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.0.0.tgz", + "integrity": "sha512-yU4hUH6AheY0dnMSaLRMfgnXhg/JUSUvrhE+lHzIiSKdEf0lyo1Ri6bkPD1CbamxZ94BqhRNCApvbvTbibGICQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.0.0", + "@angular/core": "20.0.0" + } + }, + "node_modules/@angular/cdk": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.0.1.tgz", + "integrity": "sha512-llJIyKdF9D0hJ9/PNy9A5vmayNgHr7MtQrtjpeLyPuK8qkUnxQd9Hzv5olqixRrbxxDs/Lt0l1T2ViHGy7WYhg==", + "license": "MIT", + "dependencies": { + "parse5": "^7.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^20.0.0 || ^21.0.0", + "@angular/core": "^20.0.0 || ^21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.0.0.tgz", + "integrity": "sha512-k9EDaaLYTMWkBbayUh6Tf0PJ+E0e6jRPrjOSPsOJHRh+S5BsNdLIsKJmThGXkq2wnD35+2CKPy9UQyvfaIA5KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2000.0", + "@angular-devkit/core": "20.0.0", + "@angular-devkit/schematics": "20.0.0", + "@inquirer/prompts": "7.5.1", + "@listr2/prompt-adapter-inquirer": "2.0.22", + "@schematics/angular": "20.0.0", + "@yarnpkg/lockfile": "1.1.0", + "ini": "5.0.0", + "jsonc-parser": "3.3.1", + "listr2": "8.3.3", + "npm-package-arg": "12.0.2", + "npm-pick-manifest": "10.0.0", + "pacote": "21.0.0", + "resolve": "1.22.10", + "semver": "7.7.2", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.0.0.tgz", + "integrity": "sha512-tZTvxDjx+wH74/hIpip63u4tlaXNVXkq1iVf4gk7RPQGCAYLNPDWma8X+RpXMXWikn4/mA5NS1VBBtStTbS+gg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.0.0.tgz", + "integrity": "sha512-RzS7MFNy/f8Tft0u6Q1zszzFTeki4408zsBALwmS91a8O8x/jaEvfwA7swC7RiqiX9KKmAyuBJ0qiv42v1T5dA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.0.0.tgz", + "integrity": "sha512-dPFp/YyRJkiyppnoI85mZz0CJv0ulc5MpJV16Lx0qdrRyoKmBrGmdaGEP0DOhhBLVAmJ5J2wvShvWfE2pjMMWw==", + "license": "MIT", + "dependencies": { + "@babel/core": "7.27.1", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.0.0", + "typescript": ">=5.8 <5.9" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular/core": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.0.0.tgz", + "integrity": "sha512-2UjKbTtYSY8omY+LE4G6hQ1/R4PkE6NY7/2u99TxLH/oOnc9broCH1g9ITU+n0eJURcOFeK0/w6RdSrK+di3pg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.0.0", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "node_modules/@angular/forms": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.0.0.tgz", + "integrity": "sha512-6yeb99IrNyeyj7o0bbd+n3JTZrXX2dJfdYLJH3tlXVlO9wg63bq+YR1AeM+RDCYMs+YDJis0lQpF6s+OICJv4g==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.0.0", + "@angular/core": "20.0.0", + "@angular/platform-browser": "20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/language-service": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-20.0.0.tgz", + "integrity": "sha512-lprUUz56dk3ORWGD6Z7Hmzo2MqxezW5qXaxpJHOow3+8/Jx6q+PPWCZrFSEyTtaKr/oRGzHLVCEzB+tmB980Fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@angular/localize": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.0.0.tgz", + "integrity": "sha512-sGu8QA2lj/4soCRhK1LtWc5yhBPPfb/7M+3p+jUgzPiO5ZaeDjvi/NLwJjIlBkUAfTVpRxADLJjAziRGar5gtw==", + "license": "MIT", + "dependencies": { + "@babel/core": "7.27.1", + "@types/babel__core": "7.20.5", + "tinyglobby": "^0.2.12", + "yargs": "^17.2.1" + }, + "bin": { + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.0.0", + "@angular/compiler-cli": "20.0.0" + } + }, + "node_modules/@angular/material": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.0.1.tgz", + "integrity": "sha512-ip1hLYSVi2+UtMsONnRFocof0bk+0mCXErrdfH3MLg1j5ZtJ8XEOUY9TeGqWFCkVf8J37mIAI5g2TZW1YuJLUg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "20.0.1", + "@angular/common": "^20.0.0 || ^21.0.0", + "@angular/core": "^20.0.0 || ^21.0.0", + "@angular/forms": "^20.0.0 || ^21.0.0", + "@angular/platform-browser": "^20.0.0 || ^21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material-luxon-adapter": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@angular/material-luxon-adapter/-/material-luxon-adapter-20.0.1.tgz", + "integrity": "sha512-GKR43mnBph5g47Wm2age43CIq+Npk8fV+tJQ8Dq0DnhLGhN97BGPy3V7xTyQMSi6Z+HJcHYeOUVqAvKZgmErwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^20.0.0 || ^21.0.0", + "@angular/material": "20.0.1", + "luxon": "^3.0.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.0.0.tgz", + "integrity": "sha512-FP9YjT2beF0tov0wub6+eUQqJd2MwyYqEQQ6+Qx67ukd04plIryhrcImORehrsN24DbnHkyTqhCvUyNAZs2uwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/animations": "20.0.0", + "@angular/common": "20.0.0", + "@angular/core": "20.0.0" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.0.0.tgz", + "integrity": "sha512-AACq3Ijuq59SdLDmfxWU8hYlo8O4Br9OHWNAga2W0X6p/7HlpeZZVdTlb/KGVYRKJvGpgSB10QYlRPfm215q9Q==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.0.0", + "@angular/compiler": "20.0.0", + "@angular/core": "20.0.0", + "@angular/platform-browser": "20.0.0" + } + }, + "node_modules/@angular/router": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.0.0.tgz", + "integrity": "sha512-RQ7rU4NaZDSvvOfMZQmB50q7de+jrHYb+f0ExLKBvr80B1MK3oc9VvI2BzBkGfM4aGx71MMa0UizjOiT/31kqw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.0.0", + "@angular/core": "20.0.0", + "@angular/platform-browser": "20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", + "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", + "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", + "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.3.tgz", + "integrity": "sha512-+F8CnfhuLhwUACIJMLWnjz6zvzYM2r0yeIHKlbgfw7ml8rOMJsXNXV/hyRcb3nb493gRs4WvYpQAndWj/qQmkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", + "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", + "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", + "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.3", + "@babel/plugin-transform-parameters": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", + "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.4.tgz", + "integrity": "sha512-Glp/0n8xuj+E1588otw5rjJkTXfzW7FjH3IIUrfqiZOPQCd2vbg8e+DQE8jK9g4V5/zrxFW+D9WM9gboRPELpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.1.tgz", + "integrity": "sha512-TqGF3desVsTcp3WrJGj4HfKokfCXCLcHpt4PJF0D8/iT6LPd9RS82Upw3KPeyr6B22Lfd3DO8MVrmp0oRkUDdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", + "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.3", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", + "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", + "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz", + "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.13", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", + "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", + "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", + "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", + "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", + "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", + "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", + "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.5.1.tgz", + "integrity": "sha512-5AOrZPf2/GxZ+SDRZ5WFplCA2TAQgK3OYrXCYmJL5NaTu4ECcoWFlfUZuw7Es++6Njv7iu/8vpYJhuzxUH76Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.1.6", + "@inquirer/confirm": "^5.1.10", + "@inquirer/editor": "^4.2.11", + "@inquirer/expand": "^4.0.13", + "@inquirer/input": "^4.1.10", + "@inquirer/number": "^3.0.13", + "@inquirer/password": "^4.0.13", + "@inquirer/rawlist": "^4.1.1", + "@inquirer/search": "^3.0.13", + "@inquirer/select": "^4.2.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", + "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", + "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", + "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", + "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", + "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.22.tgz", + "integrity": "sha512-hV36ZoY+xKL6pYOt1nPNnkciFkn89KZwqLhAFzJvYysAvL5uBQdiADZx/8bIDXIukzzwG0QlPYolgMzQUtKgpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/type": "^1.5.5" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 8" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.3.0.tgz", + "integrity": "sha512-LipbQobyEfQtu8WixasaFUZZ+JCGlho4OWwWIQ5ol0rB1RKkcZvypu7sS1CBvofBGVAa3vbOh8IOGQMrbmL5dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.3.0.tgz", + "integrity": "sha512-yA+9P+ZeA3vg76BLXWeUomIAjxfmSmR2eg8fueHXDg5Xe1Xmkl9JCKuHXUhtJ+mMVcH12d5k4kJBLbyXTadfGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.3.0.tgz", + "integrity": "sha512-EDYrW9kle+8wI19JCj/PhRnGoCN9bked5cdOPdo1wdgH/HzjgoLPFTn9DHlZccgTEVhp3O+bpWXdN/rWySVvjw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.3.0.tgz", + "integrity": "sha512-OeWvSgjXXZ/zmtLqqL78I3910F6UYpUubmsUU+iBHo6nTtjkpXms95rJtGrjkWQqwswKBD7xSMplbYC4LEsiPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.3.0.tgz", + "integrity": "sha512-wDd02mt5ScX4+xd6g78zKBr6ojpgCJCTrllCAabjgap5FzuETqOqaQfKhO+tJuGWv/J5q+GIds6uY7rNFueOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.3.0.tgz", + "integrity": "sha512-COotWhHJgzXULLiEjOgWQwqig6PoA+6ji6W+sDl6M1HhMXWIymEVHGs0edsVSNtsNSCAWMxJgR3asv6FNX/2EA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.3.0.tgz", + "integrity": "sha512-kqUgQH+l8HDbkAapx+aoko7Ez4X4DqkIraOqY/k0QY5EN/iialVlFpBUXh4wFXzirdmEVjbIUMrceUh0Kh8LeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", + "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.1", + "@napi-rs/nice-android-arm64": "1.0.1", + "@napi-rs/nice-darwin-arm64": "1.0.1", + "@napi-rs/nice-darwin-x64": "1.0.1", + "@napi-rs/nice-freebsd-x64": "1.0.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", + "@napi-rs/nice-linux-arm64-gnu": "1.0.1", + "@napi-rs/nice-linux-arm64-musl": "1.0.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", + "@napi-rs/nice-linux-s390x-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-musl": "1.0.1", + "@napi-rs/nice-win32-arm64-msvc": "1.0.1", + "@napi-rs/nice-win32-ia32-msvc": "1.0.1", + "@napi-rs/nice-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", + "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", + "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", + "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngtools/webpack": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.0.0.tgz", + "integrity": "sha512-3kT8PlLDvThhZxNbJWdG2qrZrUOg0tAjd7mnsOsg65/2tsBZ2HaR3fSzkHOG+Ly6SlWiS4owKWqPRGlgFuq1bw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^20.0.0", + "typescript": ">=5.8 <5.9", + "webpack": "^5.54.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", + "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", + "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz", + "integrity": "sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", + "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.5.tgz", + "integrity": "sha512-YRx7tFgLkrpFkDAzVSV5sUJydmf2ZDrW+O3IbQ1JyeMW7B0FiWroFJTnR4/fD9CsusnAn4qRUcbb5jFnZSd6uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.0.0.tgz", + "integrity": "sha512-lK5TvxEoeaoPnxM31qeNWhHUJ3kKMnRHknYhOfOmS8xfme78nS01FdU7TODLkg2p4GNEVVtXoxhj3FmrG3srKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.0.0", + "@angular-devkit/schematics": "20.0.0", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", + "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.2.tgz", + "integrity": "sha512-F2ye+n1INNhqT0MW+LfUEvTUPc/nS70vICJcxorKl7/gV9CO39+EDCw+qHNKEqvsDWk++yGVKCbzK1qLPvmC8g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", + "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", + "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", + "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", + "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.18", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz", + "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "3.10.18", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.18.tgz", + "integrity": "sha512-jOk52a1Kz+1oU5fNWwAcNe64/GsE7r/Q6ronwDox0D3ETo/cr4ICMQyeXrj7G6FPW1n8YjRoAZA2F0XBr6GicQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jasminewd2": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.13.tgz", + "integrity": "sha512-aJ3wj8tXMpBrzQ5ghIaqMisD8C3FIrcO6sDKHqFbuqAsI7yOxj0fA7MrRCPLZHIVUjERIwsMmGn/vB0UQ9u0Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jasmine": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/luxon": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-2.4.0.tgz", + "integrity": "sha512-oCavjEjRXuR6URJEtQm0eBdfsBiEcGBZbq21of8iGkeKxU1+1xgKuFPClaBZl2KB8ZZBSWlgk61tH6Mf+nvZVw==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.19.110", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.110.tgz", + "integrity": "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz", + "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/type-utils": "8.33.0", + "@typescript-eslint/utils": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.33.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz", + "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz", + "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.33.0", + "@typescript-eslint/types": "^8.33.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz", + "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz", + "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz", + "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.33.0", + "@typescript-eslint/utils": "8.33.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", + "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz", + "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.33.0", + "@typescript-eslint/tsconfig-utils": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/visitor-keys": "8.33.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.33.0", + "@typescript-eslint/types": "8.33.0", + "@typescript-eslint/typescript-estree": "8.33.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz", + "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.33.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.0.0.tgz", + "integrity": "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-loader": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", + "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": "^18.20.0 || ^20.10.0 || >=22.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5.61.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/beasties": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.4.tgz", + "integrity": "sha512-NmzN1zN1cvGccXFyZ73335+ASXwBlVWcUPssiUDIlFdfyatHPRRufjCd5w8oPaQPvVnf9ELklaCGb1gi9FBwIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", + "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.3.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clean-stack/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.0.tgz", + "integrity": "sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", + "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cp-file": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-9.1.0.tgz", + "integrity": "sha512-3scnzFj/94eb7y4wyXRWwvzLFaQp87yyfTnChIjlfYrVqp5lVO3E2hIJMeQIltUT0K2ZAB3An1qXcBmwGyvuwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cpy": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cpy/-/cpy-9.0.1.tgz", + "integrity": "sha512-D9U0DR5FjTCN3oMTcFGktanHnAG5l020yvOCR1zKILmAyPP7I/9pl6NFgRbDcmSENtbK1sQLBz1p9HIOlroiNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arrify": "^3.0.0", + "cp-file": "^9.1.0", + "globby": "^13.1.1", + "junk": "^4.0.0", + "micromatch": "^4.0.4", + "nested-error-stacks": "^2.1.0", + "p-filter": "^3.0.0", + "p-map": "^5.3.0" + }, + "engines": { + "node": "^12.20.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cpy-cli": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-4.2.0.tgz", + "integrity": "sha512-b04b+cbdr29CdpREPKw/itrfjO43Ty0Aj7wRM6M6LoE4GJxZJCk9Xp+Eu1IqztkKh3LxIBt1tDplENsa6KYprg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cpy": "^9.0.0", + "meow": "^10.1.2" + }, + "bin": { + "cpy": "cli.js" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.5.tgz", + "integrity": "sha512-V/rbdOws2gDcnCAECfPrajhuafI0WY4WumUgc8ZHwOLnvmM0doLQ+dqvVFI2qkVxQsvo6880aC9IjpyDqcwwTw==", + "dev": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", + "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.28.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz", + "integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-7.0.0.tgz", + "integrity": "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", + "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine-core": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.1.tgz", + "integrity": "sha512-w+JDABxQCkxbGGxg+a2hUVZyqUS2JKngvIyLGu/xiw2ZwgsoSB0iiecLQsQORSeaKQ6iGrCyWG86RfNDuoA7Lg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jasmine-spec-reporter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", + "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "colors": "1.4.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", + "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.1.1.tgz", + "integrity": "sha512-oxeOSBVK/jdZsiX03LhHQkO4eISSQb5GbHi6Nsw3Mw7G4u6yUgacBAftnO7q+emPBLMsrNbz1pGIrj+Jb3z17A==", + "dev": true, + "license": "MIT", + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma-coverage/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/karma-jasmine": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.2.tgz", + "integrity": "sha512-ggi84RMNQffSDmWSyyt4zxzh2CQGwsxvYYsprgyR1j8ikzIduEdOlcLvXjZGwXG/0j41KUXOWsUCBfbEHPWP9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jasmine-core": "^3.6.0" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "karma": "*" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", + "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "jasmine-core": ">=3.8", + "karma": ">=0.9", + "karma-jasmine": ">=1.1" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "3.99.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz", + "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma-junit-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", + "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "12.0.0" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/karma/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/karma/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/karma/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/karma/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", + "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/less": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.3.0.tgz", + "integrity": "sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.3.0.tgz", + "integrity": "sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "license": "ISC", + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lmdb": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.3.0.tgz", + "integrity": "sha512-MgJocUI6QEiSXQBFWLeyo1R7eQj8Rke5dlPxX0KFwli8/bsCxpM/KbXO5y0qmV/5llQ3wpneDWcTYxa+4vn8iQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.3.0", + "@lmdb/lmdb-darwin-x64": "3.3.0", + "@lmdb/lmdb-linux-arm": "3.3.0", + "@lmdb/lmdb-linux-arm64": "3.3.0", + "@lmdb/lmdb-linux-x64": "3.3.0", + "@lmdb/lmdb-win32-arm64": "3.3.0", + "@lmdb/lmdb-win32-x64": "3.3.0" + } + }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/luxon": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", + "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/material-icons": { + "version": "1.13.14", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.14.tgz", + "integrity": "sha512-kZOfc7xCC0rAT8Q3DQixYAeT+tBqZnxkseQtp2bxBxz7q5pMAC+wmit7vJn1g/l7wRU+HEPq23gER4iPjGs5Cg==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/meow": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz", + "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimist": "^1.2.2", + "camelcase-keys": "^7.0.0", + "decamelize": "^5.0.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.2", + "read-pkg-up": "^8.0.0", + "redent": "^4.0.0", + "trim-newlines": "^4.0.2", + "type-fest": "^1.2.2", + "yargs-parser": "^20.2.9" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", + "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ngx-simple-charts": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/ngx-simple-charts/-/ngx-simple-charts-20.0.0.tgz", + "integrity": "sha512-GCLO/M4HuFD9EFR+e/26z/ht93sUbqmNzuU+rWfzJ2C3Qgtr4audGDed245HnFhPReKRnq5M9sqb4WasaRVeog==", + "license": "Apache License Version 2.0", + "dependencies": { + "d3-array": "^3.0.0", + "d3-axis": "^3.0.0", + "d3-brush": "^3.0.0", + "d3-color": "^3.0.0", + "d3-ease": "^3.0.0", + "d3-format": "^3.0.0", + "d3-interpolate": "^3.0.0", + "d3-scale": "^4.0.0", + "d3-scale-chromatic": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-shape": "^3.0.0", + "d3-time-format": "^4.0.0", + "d3-transition": "^3.0.0", + "luxon": "^3.2.1", + "material-icons": "^1.11.11", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@angular/cdk": "^20.0.1", + "@angular/common": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/material": "^20.0.1", + "@angular/router": "^20.0.0", + "rxjs": "^7.4.0" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.2.0.tgz", + "integrity": "sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-install-checks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.1.tgz", + "integrity": "sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/npm-packlist": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.0.tgz", + "integrity": "sha512-rht9U6nS8WOBDc53eipZNPo5qkAV4X2rhKE2Oj1DYUQ3DieXfj0mKkVmjnf3iuNdtMd8WfLdi2L6ASkD/8a+Kg==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", + "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ordered-binary": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", + "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-3.0.0.tgz", + "integrity": "sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^5.1.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", + "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^10.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.1.0.tgz", + "integrity": "sha512-2ifK6Jb+ONoqOy5f+cYHsqvx1obHQdvIk13Jmt/5ezxP0U9p+fqd+R6O73KblGswyuzBYfetmsfK9ThMgnuPPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/piscina": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.0.0.tgz", + "integrity": "sha512-R+arufwL7sZvGjAhSMK3TfH55YdGOqhpKXkcwQJr432AAnJX/xxX19PA4QisrmJ+BTTfZVggaz6HexbkQq1l1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz", + "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0", + "read-pkg": "^6.0.0", + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/read-pkg": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", + "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", + "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^5.0.0", + "strip-indent": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz", + "integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", + "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", + "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/terser": { + "version": "5.39.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.1.tgz", + "integrity": "sha512-Mm6+uad0ZuDtcV8/4uOZQDQ8RuiC5Pu+iZRedJtF7yA/27sPL7d++In/AJKpWZlU3SYMPPkVfwetn6sgZ66pUA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", + "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-newlines": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz", + "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.0.1.tgz", + "integrity": "sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz", + "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.0.tgz", + "integrity": "sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/webpack": { + "version": "5.99.8", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz", + "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz", + "integrity": "sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.7", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.0.tgz", + "integrity": "sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlbuilder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", + "license": "MIT" + } + } +} diff --git a/frontend/src/angular/package.json b/frontend/src/angular/package.json new file mode 100644 index 00000000..0611321c --- /dev/null +++ b/frontend/src/angular/package.json @@ -0,0 +1,78 @@ +{ + "name": "trader", + "version": "0.0.0", + "license": "MIT", + "scripts": { + "ng": "ng", + "start": "ng serve --hmr --proxy-config proxy.conf.js", + "build": "ng build --localize --configuration production", + "test": "ng test --browsers ChromeHeadless --watch=false --code-coverage=true", + "test-chromium": "ng test --browsers ChromiumHeadless --watch=false --code-coverage=true", + "test-local": "ng test --browsers Chromium --watch=true", + "lint": "ng lint", + "e2e": "ng e2e", + "prebuild": "rimraf ./dist && mkdirp ./dist", + "postbuild": "npm run deploy", + "predeploy": "rimraf ../../main/resources/static/*", + "deploy": "cpy 'base/*' dist/testproject/browser/ && cpy 'dist/testproject/browser/**' ../../../backend/src/main/resources/static/", + "extract-i18n": "ng extract-i18n --format=json", + "prettier": "npx prettier --write 'src/**/*.{ts,html,scss,json}'" + }, + "private": true, + "dependencies": { + "@angular/animations": "^20.0.0", + "@angular/cdk": "^20.0.1", + "@angular/common": "^20.0.0", + "@angular/compiler": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/forms": "^20.0.0", + "@angular/localize": "^20.0.0", + "@angular/material": "^20.0.1", + "@angular/material-luxon-adapter": "^20.0.1", + "@angular/platform-browser": "^20.0.0", + "@angular/platform-browser-dynamic": "^20.0.0", + "@angular/router": "^20.0.0", + "@types/luxon": "^2.0.0", + "luxon": "^3.2.1", + "material-icons": "^1.11.11", + "ngx-simple-charts": "^20.0.0", + "rxjs": "^7.4.0", + "tslib": "^2.3.1", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^20.0.0", + "@angular/cli": "^20.0.0", + "@angular/compiler-cli": "^20.0.0", + "@angular/language-service": "^20.0.0", + "@types/jasmine": "~3.10.0", + "@types/jasminewd2": "~2.0.3", + "@types/node": "^18.0.0", + "cpy-cli": "^4.2.0", + "jasmine-core": "~4.0.0", + "jasmine-spec-reporter": "~7.0.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.1.0", + "karma-jasmine": "~4.0.0", + "karma-jasmine-html-reporter": "^1.7.0", + "karma-junit-reporter": "~2.0.0", + "mkdirp": "^1.0.4", + "npm-run-all": "^4.1.5", + "ts-node": "~10.4.0", + "typescript": "~5.8.3", + "rimraf": "^5.0.2", + "prettier": "^3.0.0", + "webpack-bundle-analyzer": "^4.5.0", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "@angular-eslint/builder": "^20.0.0-alpha.1", + "@angular-eslint/eslint-plugin": "^20.0.0-alpha.1", + "@angular-eslint/eslint-plugin-template": "^20.0.0-alpha.1", + "@angular-eslint/schematics": "^20.0.0-alpha.1", + "@angular-eslint/template-parser": "^20.0.0-alpha.1", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0" + } +} \ No newline at end of file diff --git a/src/angular/trader/proxy.conf.js b/frontend/src/angular/proxy.conf.js similarity index 84% rename from src/angular/trader/proxy.conf.js rename to frontend/src/angular/proxy.conf.js index c93440d8..c764150f 100644 --- a/src/angular/trader/proxy.conf.js +++ b/frontend/src/angular/proxy.conf.js @@ -5,7 +5,8 @@ const PROXY_CONFIG = [ "/coinbase", "/itbit", "/bitfinex", - "/myuser" + "/myuser", + "/statistics" ], target: "http://localhost:8080", secure: false diff --git a/frontend/src/angular/src/app/app-routing.module.ts b/frontend/src/angular/src/app/app-routing.module.ts new file mode 100644 index 00000000..c14eb889 --- /dev/null +++ b/frontend/src/angular/src/app/app-routing.module.ts @@ -0,0 +1,54 @@ +/*** Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { NgModule } from "@angular/core"; +import { Routes, RouterModule, PreloadAllModules } from "@angular/router"; +import { AuthGuardService } from "./services/auth-guard.service"; +import { SplashComponent } from "./splash/splash.component"; + +const routes: Routes = [ + { + path: "overview", + loadChildren: () => + import("./overview/overview.module").then((m) => m.OverviewModule), + }, + { + path: "details", + loadChildren: () => + import("./details/details.module").then((m) => m.DetailsModule), + }, + { + path: "orderbooks", + loadChildren: () => + import("./orderbooks/orderbooks.module").then((m) => m.OrderbooksModule), + canActivate: [AuthGuardService], + }, + { + path: "statistics", + loadChildren: () => + import("./statistics/statistics.module").then((m) => m.StatisticsModule), + }, + { path: "**", component: SplashComponent }, +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(routes, { + enableTracing: false, + preloadingStrategy: PreloadAllModules, + }), + ], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/frontend/src/angular/src/app/app.component.html b/frontend/src/angular/src/app/app.component.html new file mode 100644 index 00000000..0680b43f --- /dev/null +++ b/frontend/src/angular/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/src/angular/trader/src/app/app.component.scss b/frontend/src/angular/src/app/app.component.scss similarity index 100% rename from src/angular/trader/src/app/app.component.scss rename to frontend/src/angular/src/app/app.component.scss diff --git a/frontend/src/angular/src/app/app.component.spec.ts b/frontend/src/angular/src/app/app.component.spec.ts new file mode 100644 index 00000000..ba80b55d --- /dev/null +++ b/frontend/src/angular/src/app/app.component.spec.ts @@ -0,0 +1,49 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { TestBed, waitForAsync } from "@angular/core/testing"; +import { RouterTestingModule } from "@angular/router/testing"; +import { AppComponent } from "./app.component"; +describe("AppComponent", () => { + // beforeEach(async(() => { + // TestBed.configureTestingModule({ + // imports: [ + // RouterTestingModule + // ], + // declarations: [ + // AppComponent + // ], + // }).compileComponents(); + // })); + // it('should create the app', async(() => { + // const fixture = TestBed.createComponent(AppComponent); + // const app = fixture.debugElement.componentInstance; + // expect(app).toBeTruthy(); + // })); + // it(`should have as title 'app'`, async(() => { + // const fixture = TestBed.createComponent(AppComponent); + // const app = fixture.debugElement.componentInstance; + // expect(app.title).toEqual('app'); + // })); + // it('should render title in a h1 tag', async(() => { + // const fixture = TestBed.createComponent(AppComponent); + // fixture.detectChanges(); + // const compiled = fixture.debugElement.nativeElement; + // expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); + // })); + it("should calc correctly", () => { + expect(1 + 1).toEqual(2); + }); +}); diff --git a/frontend/src/angular/src/app/app.component.ts b/frontend/src/angular/src/app/app.component.ts new file mode 100644 index 00000000..f02d080b --- /dev/null +++ b/frontend/src/angular/src/app/app.component.ts @@ -0,0 +1,43 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Component, Inject, LOCALE_ID, OnInit } from "@angular/core"; +import { environment } from "./../environments/environment"; + +@Component({ + selector: "app-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.scss"], + standalone: false +}) +export class AppComponent implements OnInit { + protected title = "app"; + + constructor(@Inject(LOCALE_ID) private locale: string) {} + + ngOnInit(): void { + //console.log(window.location.href); + //console.log(this.locale); + if (environment.production && window.location.href.split("/").length > 3) { + let urlStr = "/" + window.location.href.split("/").slice(3).join("/"); + urlStr = + urlStr.indexOf(`/${this.locale}/`) !== 0 + ? `/${this.locale}` + urlStr + : urlStr; + //console.log(urlStr); + window.history.pushState({ foo: "bar" }, "", urlStr); + } + } +} diff --git a/frontend/src/angular/src/app/app.module.ts b/frontend/src/angular/src/app/app.module.ts new file mode 100644 index 00000000..63791723 --- /dev/null +++ b/frontend/src/angular/src/app/app.module.ts @@ -0,0 +1,40 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule } from "@angular/core"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; + +import { AppRoutingModule } from "./app-routing.module"; +import { AppComponent } from "./app.component"; +import { SplashComponent } from "./splash/splash.component"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { + NgxServiceModule, + SimpleChartsConfig, +} from "ngx-simple-charts/base-service"; + +@NgModule({ declarations: [AppComponent, SplashComponent], + bootstrap: [AppComponent], imports: [BrowserModule, + AppRoutingModule, + BrowserAnimationsModule, + MatProgressSpinnerModule, + NgxServiceModule.forRoot({ + tokenRefreshPath: "/myuser/refreshToken", + logoutPath: "/myuser/logout", + loginRoute: "/login", + })], providers: [provideHttpClient(withInterceptorsFromDi())] }) +export class AppModule {} diff --git a/src/angular/trader/src/app/common/authcheck.ts b/frontend/src/angular/src/app/common/authcheck.ts similarity index 87% rename from src/angular/trader/src/app/common/authcheck.ts rename to frontend/src/angular/src/app/common/authcheck.ts index 2e71ee88..1efeabfb 100644 --- a/src/angular/trader/src/app/common/authcheck.ts +++ b/frontend/src/angular/src/app/common/authcheck.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,8 +14,8 @@ limitations under the License. */ export class AuthCheck { - createdAt: Date; - hash: string; - path: string; - authorized: boolean; -} \ No newline at end of file + createdAt: Date; + hash: string; + path: string; + authorized: boolean; +} diff --git a/frontend/src/angular/src/app/common/common-statistics.ts b/frontend/src/angular/src/app/common/common-statistics.ts new file mode 100644 index 00000000..bde04069 --- /dev/null +++ b/frontend/src/angular/src/app/common/common-statistics.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export enum StatisticCurrencyPair { + bcUsd = "BcUsd", + ethUsd = "EthUsd", + lcUsd = "LcUsd", + rpUsd = "RpUsd", +} + +export enum CoinExchange { + bitfinex = "Bitfinex", + bitstamp = "Bitstamp", +} + +export class CommonStatistics { + currPair: StatisticCurrencyPair; + performance1Month: number; + performance3Month: number; + performance6Month: number; + performance1Year: number; + performance2Year: number; + performance5Year: number; + volatility1Month: number; + volatility3Month: number; + volatility6Month: number; + volatility1Year: number; + volatility2Year: number; + volatility5Year: number; + avgVolume1Month: number; + avgVolume3Month: number; + avgVolume6Month: number; + avgVolume1Year: number; + avgVolume2Year: number; + avgVolume5Year: number; + range1Month: RangeValues; + range3Month: RangeValues; + range6Month: RangeValues; + range1Year: RangeValues; + range2Year: RangeValues; + range5Year: RangeValues; +} + +export class RangeValues { + min: number; + max: number; +} diff --git a/frontend/src/angular/src/app/common/common-utils.ts b/frontend/src/angular/src/app/common/common-utils.ts new file mode 100644 index 00000000..d37f1373 --- /dev/null +++ b/frontend/src/angular/src/app/common/common-utils.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the 'License'); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an 'AS IS' BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { BitstampService } from "../services/bitstamp.service"; +import { CoinbaseService } from "../services/coinbase.service"; +import { ItbitService } from "../services/itbit.service"; +import { BitfinexService } from "../services/bitfinex.service"; + +enum MyTimeFrames { + Day = 1, + Day7 = 7, + Day30 = 30, + Day90 = 90, + Day180 = 180, + Day365 = 365, +} + +export class CommonUtils { + public LINEAR_REGRESSION = 'Linear-Reg'; + public MyTimeFrames = MyTimeFrames; + public timeframes = [ + MyTimeFrames.Day, + MyTimeFrames.Day7, + MyTimeFrames.Day30, + MyTimeFrames.Day90, + MyTimeFrames.Day180, + MyTimeFrames.Day365, + ]; + private currpairs = new Map(); + + constructor() { + const serviceBs = new BitstampService(null); + const serviceCb = new CoinbaseService(null); + const serviceIb = new ItbitService(null); + const serviceBf = new BitfinexService(null); + this.currpairs.set(serviceBs.BTCEUR, "Bitcoin Eur"); + this.currpairs.set(serviceBs.ETHEUR, "Ether Eur"); + this.currpairs.set(serviceBs.LTCEUR, "Litecoin Eur"); + this.currpairs.set(serviceBs.XRPEUR, "Ripple Eur"); + this.currpairs.set(serviceBs.BTCUSD, "Bitcoin Usd"); + this.currpairs.set(serviceBs.ETHUSD, "Ether Usd"); + this.currpairs.set(serviceBs.LTCUSD, "Litecoin Usd"); + this.currpairs.set(serviceBs.XRPUSD, "Ripple Usd"); + this.currpairs.set(serviceIb.BTCEUR, "Bitcoin Eur"); + this.currpairs.set(serviceIb.BTCUSD, "Bitcoin Usd"); + this.currpairs.set(serviceCb.BTCUSD, "Bitcoin Usd"); + this.currpairs.set(serviceCb.ETHUSD, "Ether Usd"); + this.currpairs.set(serviceCb.LTCUSD, "Litecoin Usd"); + this.currpairs.set(serviceBf.BTCUSD, "Bitcoin Usd"); + this.currpairs.set(serviceBf.ETHUSD, "Ether Usd"); + this.currpairs.set(serviceBf.LTCUSD, "Litecoin Usd"); + this.currpairs.set(serviceBf.XRPUSD, "Ripple Usd"); + } + + getCurrpairName(key: string) { + return this.currpairs.get(key); + } + + createReportUrl(timeframe: number, currpair: string): string { + let url = "/"; + if (timeframe === this.MyTimeFrames.Day7) { + url = url + currpair + "/7days/pdf"; + } else if (timeframe === this.MyTimeFrames.Day30) { + url = url + currpair + "/30days/pdf"; + } else if (timeframe === this.MyTimeFrames.Day90) { + url = url + currpair + "/90days/pdf"; + } else if (timeframe === this.MyTimeFrames.Day180) { + url = url + currpair + "/6month/pdf"; + } else if (timeframe === this.MyTimeFrames.Day365) { + url = url + currpair + "/1year/pdf"; + } else { + url = url + currpair + "/today/pdf"; + } + return url; + } +} diff --git a/frontend/src/angular/src/app/common/detail-base.ts b/frontend/src/angular/src/app/common/detail-base.ts new file mode 100644 index 00000000..f84290c1 --- /dev/null +++ b/frontend/src/angular/src/app/common/detail-base.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2019 Sven Loesekann + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { CommonUtils } from "./common-utils"; +import { ChartPoint, ChartPoints } from "ngx-simple-charts/line"; + +export class Tuple { + constructor( + private myA: A, + private myB: B, + ) {} + + // eslint-disable-next-line @typescript-eslint/naming-convention + get A() { + return this.myA; + } + // eslint-disable-next-line @typescript-eslint/naming-convention + get B() { + return this.myB; + } +} + +export abstract class DetailBase { + chartPoints: ChartPoints[] = []; + utils = new CommonUtils(); + currPair = ""; + timeframe = this.utils.MyTimeFrames.Day; + addLinReg = false; + readonly yScaleWidth = 50; + readonly xScaleHeight = 20; + + constructor(protected locale: string) {} + + protected updateChartData(values: Tuple[]): void { + const myChartPoint = values + .filter((value) => value.B > 0.009) + .map((myCP) => ({ x: new Date(myCP.A), y: myCP.B }) as ChartPoint); + let xSum=0, ySum=0 , xxSum=0, xySum=0; + for (let i = 0; i < myChartPoint.length; i++) { + xSum += i; + ySum += myChartPoint[i].y; + xxSum += i * i; + xySum += i * myChartPoint[i].y; + } + const slope = (myChartPoint.length * xySum - xSum * ySum) / (myChartPoint.length * xxSum - xSum * xSum); + const intercept = (ySum / myChartPoint.length) - (slope * xSum) / myChartPoint.length; + //console.log(slope, intercept); + const linReg = myChartPoint.map((myPoint,i) => ({x: myPoint.x, y: slope * i + intercept}) as ChartPoint); + //console.log(linReg); + this.chartPoints = [ + { + name: this.currPair, + chartPointList: myChartPoint, + yScaleWidth: this.yScaleWidth, + xScaleHeight: this.xScaleHeight, + } as ChartPoints + ]; + if(this.addLinReg) { + this.chartPoints.push({name: this.utils.LINEAR_REGRESSION, + chartPointList: linReg, + yScaleWidth: this.yScaleWidth, + xScaleHeight: this.xScaleHeight + } as ChartPoints); + } + //console.log(this.chartPoints); + } +} diff --git a/frontend/src/angular/src/app/common/my-user.ts b/frontend/src/angular/src/app/common/my-user.ts new file mode 100644 index 00000000..e5a9a7a0 --- /dev/null +++ b/frontend/src/angular/src/app/common/my-user.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export class MyUser { + _id: string; + userId: string; + password: string; + salt: string; + email: string; + btcAmount: number; + ethAmount: number; + ltcAmount: number; + xrpAmount: number; + token: string; +} diff --git a/frontend/src/angular/src/app/common/orderbook-bf.ts b/frontend/src/angular/src/app/common/orderbook-bf.ts new file mode 100644 index 00000000..e15e2690 --- /dev/null +++ b/frontend/src/angular/src/app/common/orderbook-bf.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export interface OrderbookBf { + bids: OrderBf[]; + asks: OrderBf[]; +} + +export interface OrderBf { + price: string; + amount: string; + timestamp: Date; +} diff --git a/frontend/src/angular/src/app/common/orderbook-bs.ts b/frontend/src/angular/src/app/common/orderbook-bs.ts new file mode 100644 index 00000000..15cc19fb --- /dev/null +++ b/frontend/src/angular/src/app/common/orderbook-bs.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export interface OrderbookBs { + timestamp: Date; + bids: string[][]; + asks: string[][]; +} diff --git a/frontend/src/angular/src/app/common/orderbook-ib.ts b/frontend/src/angular/src/app/common/orderbook-ib.ts new file mode 100644 index 00000000..d6023988 --- /dev/null +++ b/frontend/src/angular/src/app/common/orderbook-ib.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export interface OrderbookIb { + bids: string[][]; + asks: string[][]; +} diff --git a/frontend/src/angular/src/app/common/quote-bf.ts b/frontend/src/angular/src/app/common/quote-bf.ts new file mode 100644 index 00000000..d10c0952 --- /dev/null +++ b/frontend/src/angular/src/app/common/quote-bf.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export interface QuoteBf { + _id: string; + pair: string; + createdAt: string; + mid: number; + bid: number; + ask: number; + // eslint-disable-next-line @typescript-eslint/naming-convention + last_price: number; + low: number; + high: number; + volume: number; + timestamp: string; +} diff --git a/src/angular/trader/src/app/common/quoteBs.ts b/frontend/src/angular/src/app/common/quote-bs.ts similarity index 73% rename from src/angular/trader/src/app/common/quoteBs.ts rename to frontend/src/angular/src/app/common/quote-bs.ts index 3df04562..a5e1602a 100644 --- a/src/angular/trader/src/app/common/quoteBs.ts +++ b/frontend/src/angular/src/app/common/quote-bs.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,16 +14,16 @@ limitations under the License. */ export interface QuoteBs { - _id: string; - pair: string; - createdAt: Date; - high: number; - last: number; - timestamp: Date; - bid: number; - vwap: number; - volume: number; - low: number; - ask: number; - open: number; -} \ No newline at end of file + _id: string; + pair: string; + createdAt: string; + high: number; + last: number; + timestamp: string; + bid: number; + vwap: number; + volume: number; + low: number; + ask: number; + open: number; +} diff --git a/frontend/src/angular/src/app/common/quote-cb.ts b/frontend/src/angular/src/app/common/quote-cb.ts new file mode 100644 index 00000000..dc15166d --- /dev/null +++ b/frontend/src/angular/src/app/common/quote-cb.ts @@ -0,0 +1,198 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export interface QuoteCbSmall { + _id: string; + createdAt: string; + usd: number; + eur: number; + eth: number; + ltc: number; +} + +export interface QuoteCb { + _id: string; + createdAt: string; + aed: number; + afn: number; + all: number; + amd: number; + ang: number; + aoa: number; + ars: number; + aud: number; + awg: number; + azn: number; + bam: number; + bbd: number; + bdt: number; + bgn: number; + bhd: number; + bif: number; + bmd: number; + bnd: number; + bob: number; + brl: number; + bsd: number; + btc: number; + btn: number; + bwp: number; + byn: number; + byr: number; + bzd: number; + cad: number; + cdf: number; + chf: number; + clf: number; + clp: number; + cny: number; + cop: number; + crc: number; + cuc: number; + cve: number; + czk: number; + djf: number; + dkk: number; + dop: number; + dzd: number; + eek: number; + egp: number; + ern: number; + etb: number; + eth: number; + eur: number; + fjd: number; + fkp: number; + gbp: number; + gel: number; + ggp: number; + ghs: number; + gip: number; + gmd: number; + gnf: number; + gtq: number; + gyd: number; + hkd: number; + hnl: number; + hrk: number; + htg: number; + huf: number; + idr: number; + ils: number; + imp: number; + inr: number; + iqd: number; + isk: number; + jep: number; + jmd: number; + jod: number; + jpy: number; + kes: number; + kgs: number; + khr: number; + kmf: number; + krw: number; + kwd: number; + kyd: number; + kzt: number; + lak: number; + lbp: number; + lkr: number; + lrd: number; + lsl: number; + ltc: number; + ltl: number; + lvl: number; + lyd: number; + mad: number; + mdl: number; + mga: number; + mkd: number; + mmk: number; + mnt: number; + mop: number; + mro: number; + mtl: number; + mur: number; + mvr: number; + mwk: number; + mxn: number; + myr: number; + mzn: number; + nad: number; + ngn: number; + nio: number; + nok: number; + npr: number; + nzd: number; + omr: number; + pab: number; + pen: number; + pgk: number; + php: number; + pkr: number; + pln: number; + pyg: number; + qar: number; + ron: number; + rsd: number; + rub: number; + rwf: number; + sar: number; + sbd: number; + scr: number; + sek: number; + sgd: number; + shp: number; + sll: number; + sos: number; + srd: number; + ssp: number; + std: number; + svc: number; + szl: number; + thb: number; + tjs: number; + tmt: number; + tnd: number; + top: number; + try1: number; + ttd: number; + twd: number; + tzs: number; + uah: number; + ugx: number; + usd: number; + uyu: number; + uzs: number; + vef: number; + vnd: number; + vuv: number; + wst: number; + xaf: number; + xag: number; + xau: number; + xcd: number; + xdr: number; + xof: number; + xpd: number; + xpf: number; + xpt: number; + yer: number; + zar: number; + zmk: number; + zmw: number; + zwl: number; +} diff --git a/src/angular/trader/src/app/common/quoteIb.ts b/frontend/src/angular/src/app/common/quote-ib.ts similarity index 63% rename from src/angular/trader/src/app/common/quoteIb.ts rename to frontend/src/angular/src/app/common/quote-ib.ts index 5edc302e..3ef88e19 100644 --- a/src/angular/trader/src/app/common/quoteIb.ts +++ b/frontend/src/angular/src/app/common/quote-ib.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,21 +14,21 @@ limitations under the License. */ export interface QuoteIb { - _id: string; - createdAt: Date; - pair: string; - bid: number; - bidAmt: number; - ask: number; - askAmt: number; - lastPrice: number; - stAmt: number; - volume24h: number; - volumeToday: number; - high24h: number; - low24h: number; - openToday: number; - vwapToday: number; - vwap24h: number; - serverTimeUTC: Date; -} \ No newline at end of file + _id: string; + createdAt: string; + pair: string; + bid: number; + bidAmt: number; + ask: number; + askAmt: number; + lastPrice: number; + stAmt: number; + volume24h: number; + volumeToday: number; + high24h: number; + low24h: number; + openToday: number; + vwapToday: number; + vwap24h: number; + serverTimeUTC: string; +} diff --git a/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.html b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.html new file mode 100644 index 00000000..8509472f --- /dev/null +++ b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.html @@ -0,0 +1,81 @@ +
+ + Bitfinex + {{ currPair }} + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Last:{{ currQuote?.last_price | number : "1.2" }}High:{{ currQuote?.high | number : "1.2" }}Low:{{ currQuote?.low | number : "1.2" }}
Bid:{{ currQuote?.bid | number : "1.2" }}Ask:{{ currQuote?.ask | number : "1.2" }}Mid:{{ currQuote?.mid | number : "1.2" }}
Pair:{{ currPair }}Timestamp:{{ currQuote?.createdAt | date : "HH:mm:ss" }}Volume:{{ currQuote?.volume | number : "1.2" }}
+ +
+ +
+
{{ todayQuotes?.length }} {{ chartShow | async }}
+
+
diff --git a/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.scss b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.scss new file mode 100644 index 00000000..27fbe60e --- /dev/null +++ b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.scss @@ -0,0 +1,43 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +td { + width: 150px; +} +.radioGroup { + //margin-left: 100px; +} +.radioButton { + margin-left: 10px; +} +.chart-container { + height: calc(100vh - 250px); + width: 100%; +} +.example-fill-remaining-space { + // This fills the remaining space, by using flexbox. + // Every toolbar row uses a flexbox row layout. + flex: 1 1 auto; +} + +::ng-deep .line-linear-reg { + fill: none !important; + stroke: #7cfc00 !important; + stroke-width: 2 !important; +} + +::ng-deep .mdc-form-field > label { + cursor: pointer; +} diff --git a/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.spec.ts b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.spec.ts new file mode 100644 index 00000000..141b40cb --- /dev/null +++ b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.spec.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; +import { RouterTestingModule } from "@angular/router/testing"; +import { BitfinexService } from "../../services/bitfinex.service"; +import { BfdetailComponent } from "./bfdetail.component"; +import { BrowserModule } from "@angular/platform-browser"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { DebugElement } from "@angular/core"; +import { By } from "@angular/platform-browser"; +import { of, Observable } from "rxjs"; +import { QuoteBf } from "../../common/quote-bf"; +import { HttpClient } from "@angular/common/http"; +import { MatRadioModule } from "@angular/material/radio"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { NgxLineChartsModule } from "ngx-simple-charts/line"; +import { MatCheckboxModule } from "@angular/material/checkbox"; + +class MockBfService extends BitfinexService { + constructor(private http1: HttpClient) { + super(http1); + } + getCurrentQuote(currencypair: string): Observable { + const quoteBf: QuoteBf = { + _id: "id", + pair: "pair", + createdAt: "2018-01-01", + mid: 1, + bid: 2, + ask: 3, + last_price: 4, + low: 5, + high: 6, + volume: 7, + timestamp: "timestamp", + }; + return of(quoteBf); + } + getTodayQuotes(currencypair: string): Observable { + return of([]); + } +} + +describe("BfdetailComponent", () => { + let component: BfdetailComponent; + let fixture: ComponentFixture; + const mockService = new MockBfService(null); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [BfdetailComponent], + imports: [RouterTestingModule, + BrowserModule, + FormsModule, + ReactiveFormsModule, + BrowserAnimationsModule, + MatToolbarModule, + MatCheckboxModule, + MatRadioModule, + NgxLineChartsModule], + providers: [{ provide: BitfinexService, useValue: mockService }, provideHttpClient(withInterceptorsFromDi())] +}).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BfdetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should have value", () => { + expect(component.currQuote.mid).toBe(1); + }); + it("should show last_price", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#last_price")).nativeElement; + expect(el.textContent).toEqual("4.00"); + }); + it("should show high", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#high")).nativeElement; + expect(el.textContent).toEqual("6.00"); + }); + it("should show low", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#low")).nativeElement; + expect(el.textContent).toEqual("5.00"); + }); + it("should show bid", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#bid")).nativeElement; + expect(el.textContent).toEqual("2.00"); + }); + it("should show ask", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#ask")).nativeElement; + expect(el.textContent).toEqual("3.00"); + }); + it("should show mid", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#mid")).nativeElement; + expect(el.textContent).toEqual("1.00"); + }); + it("should show createdAt", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#createdAt")).nativeElement; + const myDate = new Date(component.currQuote.createdAt); + const dateStr = + (myDate.getMinutes().toString().length === 1 + ? "0" + myDate.getMinutes() + : myDate.getMinutes()) + + ":" + + (myDate.getSeconds().toString().length === 1 + ? "0" + myDate.getSeconds() + : myDate.getSeconds()); + expect(el.textContent.substr(3, el.textContent.length)).toEqual(dateStr); + }); + it("should show volume", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#volume")).nativeElement; + expect(el.textContent).toEqual("7.00"); + }); +}); diff --git a/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.ts b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.ts new file mode 100644 index 00000000..02f98d52 --- /dev/null +++ b/frontend/src/angular/src/app/details/bfdetail/bfdetail.component.ts @@ -0,0 +1,133 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { + Component, + OnInit, + Inject, + LOCALE_ID, + DestroyRef, + inject, +} from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + trigger, + state, + animate, + transition, + style, +} from "@angular/animations"; +import { BitfinexService } from "../../services/bitfinex.service"; +import { QuoteBf } from "../../common/quote-bf"; +import { BehaviorSubject, Observable, repeat } from "rxjs"; +import { DetailBase, Tuple } from "../../common/detail-base"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; + +@Component({ + selector: "app-bfdetail", + templateUrl: "./bfdetail.component.html", + styleUrls: ["./bfdetail.component.scss"], + animations: [ + trigger("showChart", [ + transition("false => true", [ + style({ opacity: 0 }), + animate(1000, style({ opacity: 1 })), + ]), + ]), + ], + standalone: false +}) +export class BfdetailComponent extends DetailBase implements OnInit { + public currQuote: QuoteBf; + protected chartShow = new BehaviorSubject(false); + protected todayQuotes: QuoteBf[] = []; + private readonly destroy: DestroyRef = inject(DestroyRef); + + constructor( + private route: ActivatedRoute, + private router: Router, + private serviceBf: BitfinexService, + @Inject(LOCALE_ID) private myLocale: string, + ) { + super(myLocale); + } + + ngOnInit() { + this.chartShow.next(false); + this.route.params.subscribe((params) => { + this.serviceBf + .getCurrentQuote(params.currpair) + .pipe(repeat({ delay: 10000 }), takeUntilDestroyed(this.destroy)) + .subscribe((quote) => { + this.currQuote = quote; + this.currPair = this.utils.getCurrpairName(this.currQuote.pair); + }); + this.serviceBf + .getTodayQuotes(this.route.snapshot.paramMap.get("currpair")) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe((quotes) => { + this.todayQuotes = quotes; + this.updateChartData( + quotes.map( + (quote) => + new Tuple(quote.createdAt, quote.last_price), + ), + ); + this.chartShow.next(true); + }); + }); + } + + back(): void { + this.router.navigate(["/"]); + } + + changeTf() { + this.chartShow.next(false); + const currpair = this.route.snapshot.paramMap.get("currpair"); + let quoteObserv: Observable; + if (this.timeframe === this.utils.MyTimeFrames.Day7) { + quoteObserv = this.serviceBf.get7DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day30) { + quoteObserv = this.serviceBf.get30DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day90) { + quoteObserv = this.serviceBf.get90DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day180) { + quoteObserv = this.serviceBf.get6MonthsQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day365) { + quoteObserv = this.serviceBf.get1YearQuotes(currpair); + } else { + quoteObserv = this.serviceBf.getTodayQuotes(currpair); + } + + quoteObserv.pipe(takeUntilDestroyed(this.destroy)).subscribe((quotes) => { + this.todayQuotes = quotes; + this.updateChartData( + quotes.map( + (quote) => + new Tuple(quote.createdAt, quote.last_price), + ), + ); + this.chartShow.next(true); + }); + } + + showReport() { + const currpair = this.route.snapshot.paramMap.get("currpair"); + const url = + "/bitfinex" + this.utils.createReportUrl(this.timeframe, currpair); + window.open(url); + } +} diff --git a/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.html b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.html new file mode 100644 index 00000000..e0197e48 --- /dev/null +++ b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.html @@ -0,0 +1,85 @@ +
+ + Bitstamp + {{ currPair }} + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Last:{{ currQuote?.last | number : "1.2" }}High:{{ currQuote?.high | number : "1.2" }}Low:{{ currQuote?.low | number : "1.2" }}
Bid:{{ currQuote?.bid | number : "1.2" }}Ask:{{ currQuote?.ask | number : "1.2" }}Open:{{ currQuote?.open | number : "1.2" }}
Vwap:{{ currQuote?.vwap | number : "1.2" }}Pair:{{ currPair }}Timestamp:{{ currQuote?.createdAt | date : "HH:mm:ss" }}
Volume:{{ currQuote?.volume | number : "1.2" }}
+ +
+ +
+
{{ todayQuotes?.length }} {{ chartShow | async }}
+
+
diff --git a/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.scss b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.scss new file mode 100644 index 00000000..9d8006c9 --- /dev/null +++ b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +td { + width: 150px; +} +.radioGroup { + //margin-left: 100px; +} +.radioButton { + margin-left: 10px; +} +.chart-container { + height: calc(100vh - 250px); + width: 100%; +} +.example-fill-remaining-space { + // This fills the remaining space, by using flexbox. + // Every toolbar row uses a flexbox row layout. + flex: 1 1 auto; +} +::ng-deep .line-linear-reg { + fill: none !important; + stroke: #7cfc00 !important; + stroke-width: 2 !important; +} +::ng-deep .mdc-form-field > label { + cursor: pointer; +} diff --git a/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.spec.ts b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.spec.ts new file mode 100644 index 00000000..d9a84a65 --- /dev/null +++ b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.spec.ts @@ -0,0 +1,149 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { HttpClient, provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { DebugElement } from "@angular/core"; +import { By } from "@angular/platform-browser"; +import { of, Observable } from "rxjs"; +import { BsdetailComponent } from "./bsdetail.component"; +import { RouterTestingModule } from "@angular/router/testing"; +import { BitstampService } from "../../services/bitstamp.service"; +import { QuoteBs } from "../../common/quote-bs"; +import { MatRadioModule } from "@angular/material/radio"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { NgxLineChartsModule } from "ngx-simple-charts/line"; +import { MatCheckboxModule } from "@angular/material/checkbox"; + +class MockService extends BitstampService { + constructor(private http1: HttpClient) { + super(http1); + } + + getCurrentQuote(currencypair: string): Observable { + const result: QuoteBs = { + _id: "_id", + pair: "pair", + createdAt: "2018-01-01", + high: 1, + last: 2, + timestamp: "2018-01-01", + bid: 3, + vwap: 4, + volume: 5, + low: 6, + ask: 7, + open: 8, + }; + return of(result); + } + + getTodayQuotes(currencypair: string): Observable { + return of([]); + } +} + +describe("BsdetailComponent", () => { + let component: BsdetailComponent; + let fixture: ComponentFixture; + const mockService = new MockService(null); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [BsdetailComponent], + imports: [RouterTestingModule, + BrowserModule, + FormsModule, + ReactiveFormsModule, + BrowserAnimationsModule, + MatToolbarModule, + MatCheckboxModule, + MatRadioModule, + NgxLineChartsModule], + providers: [{ provide: BitstampService, useValue: mockService }, provideHttpClient(withInterceptorsFromDi())] +}).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BsdetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + it("should have value", () => { + expect(component.currQuote.ask).toBe(7); + }); + it("should show last", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#last")).nativeElement; + expect(el.textContent).toEqual("2.00"); + }); + it("should show high", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#high")).nativeElement; + expect(el.textContent).toEqual("1.00"); + }); + it("should show low", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#low")).nativeElement; + expect(el.textContent).toEqual("6.00"); + }); + it("should show bid", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#bid")).nativeElement; + expect(el.textContent).toEqual("3.00"); + }); + it("should show ask", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#ask")).nativeElement; + expect(el.textContent).toEqual("7.00"); + }); + it("should show open", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#open")).nativeElement; + expect(el.textContent).toEqual("8.00"); + }); + it("should show vwap", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#vwap")).nativeElement; + expect(el.textContent).toEqual("4.00"); + }); + it("should show createdAt", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#createdAt")).nativeElement; + const myDate = new Date(component.currQuote.createdAt); + const dateStr = + (myDate.getMinutes().toString().length === 1 + ? "0" + myDate.getMinutes() + : myDate.getMinutes()) + + ":" + + (myDate.getSeconds().toString().length === 1 + ? "0" + myDate.getSeconds() + : myDate.getSeconds()); + expect(el.textContent.substr(3, el.textContent.length)).toEqual(dateStr); + }); + it("should show volume", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#volume")).nativeElement; + expect(el.textContent).toEqual("5.00"); + }); +}); diff --git a/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.ts b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.ts new file mode 100644 index 00000000..d120d7cc --- /dev/null +++ b/frontend/src/angular/src/app/details/bsdetail/bsdetail.component.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { + Component, + OnInit, + LOCALE_ID, + Inject, + DestroyRef, + inject, +} from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + trigger, + state, + animate, + transition, + style, +} from "@angular/animations"; +import { BehaviorSubject, Observable, repeat } from "rxjs"; +import { BitstampService } from "../../services/bitstamp.service"; +import { QuoteBs } from "../../common/quote-bs"; +import { DetailBase, Tuple } from "src/app/common/detail-base"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; + +@Component({ + selector: "app-bsdetail", + templateUrl: "./bsdetail.component.html", + styleUrls: ["./bsdetail.component.scss"], + animations: [ + trigger("showChart", [ + transition("false => true", [ + style({ opacity: 0 }), + animate(1000, style({ opacity: 1 })), + ]), + ]), + ], + standalone: false +}) +export class BsdetailComponent extends DetailBase implements OnInit { + public currQuote: QuoteBs; + protected chartShow = new BehaviorSubject(false); + protected todayQuotes: QuoteBs[] = []; + private readonly destroy: DestroyRef = inject(DestroyRef); + + constructor( + private route: ActivatedRoute, + private router: Router, + private serviceBs: BitstampService, + @Inject(LOCALE_ID) private myLocale: string, + ) { + super(myLocale); + } + + ngOnInit() { + this.chartShow.next(false); + this.route.params.subscribe((params) => { + this.serviceBs + .getCurrentQuote(params.currpair) + .pipe(repeat({ delay: 10000 }), takeUntilDestroyed(this.destroy)) + .subscribe((quote) => { + this.currQuote = quote; + this.currPair = this.utils.getCurrpairName(this.currQuote.pair); + }); + this.serviceBs + .getTodayQuotes(this.route.snapshot.paramMap.get("currpair")) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe((quotes) => { + this.todayQuotes = quotes; + this.updateChartData( + quotes.map( + (quote) => new Tuple(quote.createdAt, quote.last), + ), + ); + this.chartShow.next(true); + }); + }); + } + + back(): void { + this.router.navigate(["/"]); + } + + changeTf() { + this.chartShow.next(false); + const currpair = this.route.snapshot.paramMap.get("currpair"); + let quoteObserv: Observable; + if (this.timeframe === this.utils.MyTimeFrames.Day7) { + quoteObserv = this.serviceBs.get7DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day30) { + quoteObserv = this.serviceBs.get30DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day90) { + quoteObserv = this.serviceBs.get90DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day180) { + quoteObserv = this.serviceBs.get6MonthsQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day365) { + quoteObserv = this.serviceBs.get1YearQuotes(currpair); + } else { + quoteObserv = this.serviceBs.getTodayQuotes(currpair); + } + + quoteObserv.pipe(takeUntilDestroyed(this.destroy)).subscribe((quotes) => { + this.todayQuotes = quotes; + this.updateChartData( + quotes.map( + (quote) => new Tuple(quote.createdAt, quote.last), + ), + ); + this.chartShow.next(true); + }); + } + + showReport() { + const currpair = this.route.snapshot.paramMap.get("currpair"); + const url = + "/bitstamp" + this.utils.createReportUrl(this.timeframe, currpair); + window.open(url); + } +} diff --git a/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.html b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.html new file mode 100644 index 00000000..4b5521fe --- /dev/null +++ b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.html @@ -0,0 +1,94 @@ +
+ + Coinbase + {{ myCurrPair }} + + + +
+ + + @if (currpair === BTCUSD) { + + + + + + + + + + + + + + + } + @if (currpair === ETHUSD) { + + + + + + + + + + + + + + + } + @if (currpair === LTCUSD) { + + + + + + + + + + + + + + + } + +
Pair:{{ myCurrPair }}Last Usd:{{ currQuote?.usd | number : "1.2" }}Last Eur:{{ currQuote?.eur | number : "1.2" }}Last Yen:{{ currQuote?.jpy | number : "1.2" }}Last Pound:{{ currQuote?.gbp | number : "1.2" }}Timestamp:{{ currQuote?.createdAt | date : "HH:mm:ss" }}
Pair:{{ myCurrPair }}Last Usd:{{ currQuote?.usd / currQuote?.eth | number : "1.2" }}Last Eur:{{ currQuote?.eur / currQuote?.eth | number : "1.2" }}Last Yen:{{ currQuote?.jpy / currQuote?.eth | number : "1.2" }}Last Pound:{{ currQuote?.gbp / currQuote?.eth | number : "1.2" }}Timestamp:{{ currQuote?.createdAt | date : "HH:mm:ss" }}
Pair:{{ myCurrPair }}Last Usd:{{ currQuote?.usd / currQuote?.ltc | number : "1.2" }}Last Eur:{{ currQuote?.eur / currQuote?.ltc | number : "1.2" }}Last Yen:{{ currQuote?.jpy / currQuote?.ltc | number : "1.2" }}Last Pound:{{ currQuote?.gbp / currQuote?.ltc | number : "1.2" }}Timestamp:{{ currQuote?.createdAt | date : "HH:mm:ss" }}
+ +
+ +
+
{{ todayQuotes?.length }} {{ chartShow | async }}
+
+
diff --git a/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.scss b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.scss new file mode 100644 index 00000000..9d8006c9 --- /dev/null +++ b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +td { + width: 150px; +} +.radioGroup { + //margin-left: 100px; +} +.radioButton { + margin-left: 10px; +} +.chart-container { + height: calc(100vh - 250px); + width: 100%; +} +.example-fill-remaining-space { + // This fills the remaining space, by using flexbox. + // Every toolbar row uses a flexbox row layout. + flex: 1 1 auto; +} +::ng-deep .line-linear-reg { + fill: none !important; + stroke: #7cfc00 !important; + stroke-width: 2 !important; +} +::ng-deep .mdc-form-field > label { + cursor: pointer; +} diff --git a/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.spec.ts b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.spec.ts new file mode 100644 index 00000000..3ceaa8bb --- /dev/null +++ b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.spec.ts @@ -0,0 +1,301 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { HttpClient, provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { DebugElement } from "@angular/core"; +import { By } from "@angular/platform-browser"; +import { of, Observable } from "rxjs"; +import { RouterTestingModule } from "@angular/router/testing"; +import { CbdetailComponent } from "./cbdetail.component"; +import { CoinbaseService } from "../../services/coinbase.service"; +import { QuoteCb, QuoteCbSmall } from "../../common/quote-cb"; +import { MatRadioModule } from "@angular/material/radio"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { NgxLineChartsModule } from "ngx-simple-charts/line"; +import { MatCheckboxModule } from "@angular/material/checkbox"; + +//{"AED":30446.14,"AFN":588333.46,"ALL":894939.58,"AMD":4014504.15,"ANG":14884.19,"AOA":1903986.64,"ARS":201164.55,"AUD":11028.00,"AWG":14812.73,"AZN":14111.65,"BAM":13754.33,"BBD":16577.56,"BDT":703178.24,"BGN":13738.65,"BHD":3127.713,"BIF":14578207,"BMD":8288.78,"BND":11038.17,"BOB":57299.09,"BRL":30523.42,"BSD":8288.78,"BTC":1.00000000,"BTN":562230.25,"BWP":82310.36,"BYN":16605.18,"BYR":166051782,"BZD":16668.31,"CAD":10600.94,"CDF":13372478.79,"CHF":8298.63,"CLF":191.1393,"CLP":5225247,"CNY":52827.71,"COP":23782499.13,"CRC":4694786.20,"CUC":8288.78,"CVE":775485.82,"CZK":179273.32,"DJF":1475817,"DKK":52310.24,"DOP":411082.04,"DZD":962037.25,"EEK":121129.67,"EGP":148203.39,"ERN":125081.83,"ETB":228217.78,"ETH":11.88848600,"EUR":7031.92,"FJD":17294.15,"FKP":6147.58,"GBP":6168.36,"GEL":20167.53,"GGP":6147.58,"GHS":38021.02,"GIP":6147.58,"GMD":391188.97,"GNF":74815772,"GTQ":61592.53,"GYD":1732214.33,"HKD":65075.63,"HNL":197841.79,"HRK":51863.00,"HTG":534996.18,"HUF":2218668,"IDR":116699462.10,"ILS":29762.94,"IMP":6147.58,"INR":561108.96,"IQD":9883961.704,"ISK":865329,"JEP":6147.58,"JMD":1040265.51,"JOD":5879.248,"JPY":913792,"KES":831474.76,"KGS":566779.74,"KHR":33699278.41,"KMF":3410419,"KRW":8937626,"KWD":2503.236,"KYD":6910.78,"KZT":2728583.49,"LAK":69082422.47,"LBP":12545482.97,"LKR":1310953.44,"LRD":1097851.25,"LSL":104179.10,"LTC":59.97001499,"LTL":26730.03,"LVL":5439.60,"LYD":11217.761,"MAD":78191.38,"MDL":137380.10,"MGA":26909109.4,"MKD":432477.11,"MMK":11215133.78,"MNT":19877232.78,"MOP":67045.52,"MRO":2950805.3,"MTL":5667.35,"MUR":283907.29,"MVR":128476.43,"MWK":5967465.72,"MXN":162421.13,"MYR":32885.47,"MZN":498404.34,"NAD":101721.15,"NGN":2982929.39,"NIO":260051.50,"NOK":67005.34,"NPR":899720.93,"NZD":12017.95,"OMR":3191.172,"PAB":8288.78,"PEN":27118.90,"PGK":27070.60,"PHP":432715.76,"PKR":958398.48,"PLN":30006.63,"PYG":46456954,"QAR":30179.44,"RON":32565.03,"RSD":829270.68,"RUB":511397.00,"RWF":7208517,"SAR":31089.97,"SBD":64805.29,"SCR":111529.28,"SEK":72193.08,"SGD":11109.47,"SHP":6147.58,"SLL":63846448.73,"SOS":4795653.79,"SRD":61900.61,"SSP":1079724.66,"STD":171355480.85,"SVC":72563.56,"SZL":103926.13,"THB":265294.84,"TJS":74424.43,"TMT":29010.61,"TND":20867.841,"TOP":18875.90,"TRY":36606.32,"TTD":55889.17,"TWD":247444.95,"TZS":18913338.20,"UAH":217501.73,"UGX":30811053,"USD":8288.78,"UYU":253198.76,"UZS":66551443.50,"VEF":579489331.75,"VND":188732016,"VUV":890740,"WST":21341.63,"XAF":4607113,"XAG":506,"XAU":6,"XCD":22400.84,"XDR":5808,"XOF":4607113,"XPD":8,"XPF":838126,"XPT":9,"YER":2075192.57,"ZAR":103133.20,"ZMK":43541585.11,"ZMW":83306.10,"ZWL":2671929.77,"_id":{"timestamp":1526498426,"machineIdentifier":694106,"processIdentifier":3590,"counter":15517016,"date":"2018-05-16T19:20:26.000+0000","time":1526498426000,"timeSecond":1526498426},"createdAt":"2018-05-16T19:20:26.320+0000"}; +class MockService extends CoinbaseService { + getCurrentQuote(): Observable { + const result1 = { + AED: 30446.14, + AFN: 588333.46, + ALL: 894939.58, + AMD: 4014504.15, + ANG: 14884.19, + AOA: 1903986.64, + ARS: 201164.55, + AUD: 11028.0, + AWG: 14812.73, + AZN: 14111.65, + BAM: 13754.33, + BBD: 16577.56, + BDT: 703178.24, + BGN: 13738.65, + BHD: 3127.713, + BIF: 14578207, + BMD: 8288.78, + BND: 11038.17, + BOB: 57299.09, + BRL: 30523.42, + BSD: 8288.78, + BTC: 1.0, + BTN: 562230.25, + BWP: 82310.36, + BYN: 16605.18, + BYR: 166051782, + BZD: 16668.31, + CAD: 10600.94, + CDF: 13372478.79, + CHF: 8298.63, + CLF: 191.1393, + CLP: 5225247, + CNY: 52827.71, + COP: 23782499.13, + CRC: 4694786.2, + CUC: 8288.78, + CVE: 775485.82, + CZK: 179273.32, + DJF: 1475817, + DKK: 52310.24, + DOP: 411082.04, + DZD: 962037.25, + EEK: 121129.67, + EGP: 148203.39, + ERN: 125081.83, + ETB: 228217.78, + ETH: 11.888486, + EUR: 7031.92, + FJD: 17294.15, + FKP: 6147.58, + GBP: 6168.36, + GEL: 20167.53, + GGP: 6147.58, + GHS: 38021.02, + GIP: 6147.58, + GMD: 391188.97, + GNF: 74815772, + GTQ: 61592.53, + GYD: 1732214.33, + HKD: 65075.63, + HNL: 197841.79, + HRK: 51863.0, + HTG: 534996.18, + HUF: 2218668, + IDR: 116699462.1, + ILS: 29762.94, + IMP: 6147.58, + INR: 561108.96, + IQD: 9883961.704, + ISK: 865329, + JEP: 6147.58, + JMD: 1040265.51, + JOD: 5879.248, + JPY: 913792, + KES: 831474.76, + KGS: 566779.74, + KHR: 33699278.41, + KMF: 3410419, + KRW: 8937626, + KWD: 2503.236, + KYD: 6910.78, + KZT: 2728583.49, + LAK: 69082422.47, + LBP: 12545482.97, + LKR: 1310953.44, + LRD: 1097851.25, + LSL: 104179.1, + LTC: 59.97001499, + LTL: 26730.03, + LVL: 5439.6, + LYD: 11217.761, + MAD: 78191.38, + MDL: 137380.1, + MGA: 26909109.4, + MKD: 432477.11, + MMK: 11215133.78, + MNT: 19877232.78, + MOP: 67045.52, + MRO: 2950805.3, + MTL: 5667.35, + MUR: 283907.29, + MVR: 128476.43, + MWK: 5967465.72, + MXN: 162421.13, + MYR: 32885.47, + MZN: 498404.34, + NAD: 101721.15, + NGN: 2982929.39, + NIO: 260051.5, + NOK: 67005.34, + NPR: 899720.93, + NZD: 12017.95, + OMR: 3191.172, + PAB: 8288.78, + PEN: 27118.9, + PGK: 27070.6, + PHP: 432715.76, + PKR: 958398.48, + PLN: 30006.63, + PYG: 46456954, + QAR: 30179.44, + RON: 32565.03, + RSD: 829270.68, + RUB: 511397.0, + RWF: 7208517, + SAR: 31089.97, + SBD: 64805.29, + SCR: 111529.28, + SEK: 72193.08, + SGD: 11109.47, + SHP: 6147.58, + SLL: 63846448.73, + SOS: 4795653.79, + SRD: 61900.61, + SSP: 1079724.66, + STD: 171355480.85, + SVC: 72563.56, + SZL: 103926.13, + THB: 265294.84, + TJS: 74424.43, + TMT: 29010.61, + TND: 20867.841, + TOP: 18875.9, + TRY: 36606.32, + TTD: 55889.17, + TWD: 247444.95, + TZS: 18913338.2, + UAH: 217501.73, + UGX: 30811053, + USD: 8288.78, + UYU: 253198.76, + UZS: 66551443.5, + VEF: 579489331.75, + VND: 188732016, + VUV: 890740, + WST: 21341.63, + XAF: 4607113, + XAG: 506, + XAU: 6, + XCD: 22400.84, + XDR: 5808, + XOF: 4607113, + XPD: 8, + XPF: 838126, + XPT: 9, + YER: 2075192.57, + ZAR: 103133.2, + ZMK: 43541585.11, + ZMW: 83306.1, + ZWL: 2671929.77, + _id: "_id", + createdAt: "2018-05-16T19:20:26.320+0000", + }; + const result = this.lowercaseKeys1(result1); + return of(result); + } + + lowercaseKeys1(quote: any): QuoteCb { + for (const p in quote) { + if (quote.hasOwnProperty(p) && p !== "_id" && p !== "createdAt") { + quote[p.toLowerCase()] = quote[p]; + } + } + return quote; + } + + getTodayQuotes(): Observable { + return of([]); + } +} + +describe("CbdetailComponent", () => { + let component: CbdetailComponent; + let fixture: ComponentFixture; + const mockService = new MockService(null); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [CbdetailComponent], + imports: [RouterTestingModule, + BrowserModule, + FormsModule, + ReactiveFormsModule, + BrowserAnimationsModule, + MatToolbarModule, + MatRadioModule, + MatCheckboxModule, + NgxLineChartsModule], + providers: [{ provide: CoinbaseService, useValue: mockService }, provideHttpClient(withInterceptorsFromDi())] +}).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CbdetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + it("should have value", () => { + expect(component.currQuote.aed).toBe(30446.14); + }); + it("should show usd", () => { + component.currpair = "btcusd"; + fixture.autoDetectChanges(); + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#usd")).nativeElement; + expect(el.textContent).toEqual("8,288.78"); + }); + it("should show eur", () => { + component.currpair = "btcusd"; + fixture.autoDetectChanges(); + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#eur")).nativeElement; + expect(el.textContent).toEqual("7,031.92"); + }); + it("should show gbp", () => { + component.currpair = "btcusd"; + fixture.autoDetectChanges(); + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#gbp")).nativeElement; + expect(el.textContent).toEqual("6,168.36"); + }); + + it("should show createdAt", () => { + component.currpair = "btcusd"; + fixture.autoDetectChanges(); + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#createdAt")).nativeElement; + const myDate = new Date("2018-05-16T19:20:26"); + const timezoneOffset = myDate.getTimezoneOffset() / 60; + const dateStr = + (myDate.getMinutes().toString().length === 1 + ? "0" + myDate.getMinutes() + : myDate.getMinutes()) + + ":" + + (myDate.getSeconds().toString().length === 1 + ? "0" + myDate.getSeconds() + : myDate.getSeconds()); + expect(el.textContent.substr(3, el.textContent.length)).toEqual(dateStr); + }); +}); diff --git a/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.ts b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.ts new file mode 100644 index 00000000..ddecbbbb --- /dev/null +++ b/frontend/src/angular/src/app/details/cbdetail/cbdetail.component.ts @@ -0,0 +1,174 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { + Component, + OnInit, + LOCALE_ID, + Inject, + DestroyRef, + inject, +} from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + trigger, + state, + animate, + transition, + style, +} from "@angular/animations"; +import { BehaviorSubject, Observable, repeat } from "rxjs"; +import { QuoteCb, QuoteCbSmall } from "../../common/quote-cb"; +import { CoinbaseService } from "../../services/coinbase.service"; +import { DetailBase, Tuple } from "src/app/common/detail-base"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; + +@Component({ + selector: "app-cbdetail", + templateUrl: "./cbdetail.component.html", + styleUrls: ["./cbdetail.component.scss"], + animations: [ + trigger("showChart", [ + transition("false => true", [ + style({ opacity: 0 }), + animate(1000, style({ opacity: 1 })), + ]), + ]), + ], + standalone: false +}) +export class CbdetailComponent extends DetailBase implements OnInit { + public currpair: string; + public currQuote: QuoteCb; + // eslint-disable-next-line @typescript-eslint/naming-convention + readonly BTCUSD: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + readonly ETHUSD: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + readonly LTCUSD: string; + protected chartShow = new BehaviorSubject(false); + protected todayQuotes: QuoteCbSmall[] = []; + protected myCurrPair = ""; + private readonly destroy: DestroyRef = inject(DestroyRef); + + constructor( + private route: ActivatedRoute, + private router: Router, + private serviceCb: CoinbaseService, + @Inject(LOCALE_ID) private myLocale: string, + ) { + super(myLocale); + this.BTCUSD = this.serviceCb.BTCUSD; + this.ETHUSD = this.serviceCb.ETHUSD; + this.LTCUSD = this.serviceCb.LTCUSD; + } + + ngOnInit() { + this.chartShow.next(false); + this.route.params.subscribe((params) => { + this.currpair = params.currpair; + this.myCurrPair = this.utils.getCurrpairName(this.currpair); + this.serviceCb + .getCurrentQuote() + .pipe(repeat({ delay: 10000 }), takeUntilDestroyed(this.destroy)) + .subscribe((quote) => (this.currQuote = quote)); + this.serviceCb + .getTodayQuotes() + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe((quotes) => { + this.todayQuotes = quotes; + if (this.currpair === this.serviceCb.BTCUSD) { + this.updateChartData( + quotes.map( + (quote) => + new Tuple(quote.createdAt, quote.usd), + ), + ); + } else if (this.currpair === this.serviceCb.ETHUSD) { + this.updateChartData( + quotes.map( + (quote) => + new Tuple( + quote.createdAt, + quote.usd / quote.eth, + ), + ), + ); + } else if (this.currpair === this.serviceCb.LTCUSD) { + this.updateChartData( + quotes.map( + (quote) => + new Tuple( + quote.createdAt, + quote.usd / quote.ltc, + ), + ), + ); + } + this.chartShow.next(true); + }); + }); + } + + back(): void { + this.router.navigate(["/"]); + } + + changeTf() { + this.chartShow.next(false); + this.currpair = this.route.snapshot.paramMap.get("currpair"); + let quoteObserv: Observable; + if (this.timeframe === this.utils.MyTimeFrames.Day7) { + quoteObserv = this.serviceCb.get7DayQuotes(); + } else if (this.timeframe === this.utils.MyTimeFrames.Day30) { + quoteObserv = this.serviceCb.get30DayQuotes(); + } else if (this.timeframe === this.utils.MyTimeFrames.Day90) { + quoteObserv = this.serviceCb.get90DayQuotes(); + } else if (this.timeframe === this.utils.MyTimeFrames.Day180) { + quoteObserv = this.serviceCb.get6MonthsQuotes(); + } else if (this.timeframe === this.utils.MyTimeFrames.Day365) { + quoteObserv = this.serviceCb.get1YearQuotes(); + } else { + quoteObserv = this.serviceCb.getTodayQuotes(); + } + + quoteObserv.pipe(takeUntilDestroyed(this.destroy)).subscribe((quotes) => { + this.todayQuotes = quotes; + + if (this.currpair === this.serviceCb.BTCUSD) { + this.updateChartData( + quotes.map( + (quote) => new Tuple(quote.createdAt, quote.usd), + ), + ); + } else if (this.currpair === this.serviceCb.ETHUSD) { + this.updateChartData( + quotes.map( + (quote) => + new Tuple(quote.createdAt, quote.usd / quote.eth), + ), + ); + } else if (this.currpair === this.serviceCb.LTCUSD) { + this.updateChartData( + quotes.map( + (quote) => + new Tuple(quote.createdAt, quote.usd / quote.ltc), + ), + ); + } + this.chartShow.next(true); + }); + } +} diff --git a/frontend/src/angular/src/app/details/details-routing.module.ts b/frontend/src/angular/src/app/details/details-routing.module.ts new file mode 100644 index 00000000..81419216 --- /dev/null +++ b/frontend/src/angular/src/app/details/details-routing.module.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { NgModule } from "@angular/core"; +import { Routes, RouterModule } from "@angular/router"; +import { BsdetailComponent } from "./bsdetail/bsdetail.component"; +import { IbdetailComponent } from "./ibdetail/ibdetail.component"; +import { CbdetailComponent } from "./cbdetail/cbdetail.component"; +import { BfdetailComponent } from "./bfdetail/bfdetail.component"; + +const routes: Routes = [ + { path: "bsdetail/:currpair", component: BsdetailComponent }, + { path: "ibdetail/:currpair", component: IbdetailComponent }, + { path: "cbdetail/:currpair", component: CbdetailComponent }, + { path: "bfdetail/:currpair", component: BfdetailComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class DetailsRoutingModule {} diff --git a/frontend/src/angular/src/app/details/details.module.ts b/frontend/src/angular/src/app/details/details.module.ts new file mode 100644 index 00000000..0f3c97a4 --- /dev/null +++ b/frontend/src/angular/src/app/details/details.module.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { DetailsRoutingModule } from "./details-routing.module"; +import { ReactiveFormsModule, FormsModule } from "@angular/forms"; +import { IbdetailComponent } from "./ibdetail/ibdetail.component"; +import { CbdetailComponent } from "./cbdetail/cbdetail.component"; +import { BsdetailComponent } from "./bsdetail/bsdetail.component"; +import { BfdetailComponent } from "./bfdetail/bfdetail.component"; +import { MatButtonModule } from "@angular/material/button"; +import { MatRadioModule } from "@angular/material/radio"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { NgxLineChartsModule } from "ngx-simple-charts/line"; +import { MatCheckboxModule } from "@angular/material/checkbox"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + MatToolbarModule, + MatCheckboxModule, + MatRadioModule, + MatButtonModule, + DetailsRoutingModule, + NgxLineChartsModule, + ], + declarations: [ + IbdetailComponent, + CbdetailComponent, + BsdetailComponent, + BfdetailComponent, + ], +}) +export class DetailsModule {} diff --git a/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.html b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.html new file mode 100644 index 00000000..35326b07 --- /dev/null +++ b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.html @@ -0,0 +1,85 @@ +
+ + Itbit + {{ currPair }} + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Last:{{ currQuote?.lastPrice | number : "1.2" }}High:{{ currQuote?.high24h | number : "1.2" }}Low:{{ currQuote?.low24h | number : "1.2" }}
Bid:{{ currQuote?.bid | number : "1.2" }}Ask:{{ currQuote?.ask | number : "1.2" }}Open:{{ currQuote?.openToday | number : "1.2" }}
Vwap:{{ currQuote?.vwap24h | number : "1.2" }}Pair:{{ currPair }}Timestamp:{{ currQuote?.createdAt | date : "HH:mm:ss" }}
Volume:{{ currQuote?.volume24h | number : "1.2" }}
+ +
+ +
+
{{ todayQuotes?.length }} {{ chartShow | async }}
+
+
diff --git a/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.scss b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.scss new file mode 100644 index 00000000..9d8006c9 --- /dev/null +++ b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +td { + width: 150px; +} +.radioGroup { + //margin-left: 100px; +} +.radioButton { + margin-left: 10px; +} +.chart-container { + height: calc(100vh - 250px); + width: 100%; +} +.example-fill-remaining-space { + // This fills the remaining space, by using flexbox. + // Every toolbar row uses a flexbox row layout. + flex: 1 1 auto; +} +::ng-deep .line-linear-reg { + fill: none !important; + stroke: #7cfc00 !important; + stroke-width: 2 !important; +} +::ng-deep .mdc-form-field > label { + cursor: pointer; +} diff --git a/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.spec.ts b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.spec.ts new file mode 100644 index 00000000..290d6238 --- /dev/null +++ b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.spec.ts @@ -0,0 +1,155 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { HttpClient, provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { DebugElement } from "@angular/core"; +import { By } from "@angular/platform-browser"; +import { of, Observable } from "rxjs"; +import { RouterTestingModule } from "@angular/router/testing"; +import { IbdetailComponent } from "./ibdetail.component"; +import { ItbitService } from "../../services/itbit.service"; +import { QuoteIb } from "../../common/quote-ib"; +import { MatRadioModule } from "@angular/material/radio"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { NgxLineChartsModule } from "ngx-simple-charts/line"; +import { MatCheckboxModule } from "@angular/material/checkbox"; + +class MockService extends ItbitService { + constructor(private http1: HttpClient) { + super(http1); + } + + getCurrentQuote(currencypair: string): Observable { + const result: QuoteIb = { + _id: "id", + createdAt: "2018-01-01", + pair: "pair", + bid: 1, + bidAmt: 2, + ask: 3, + askAmt: 4, + lastPrice: 5, + stAmt: 6, + volume24h: 7, + volumeToday: 8, + high24h: 9, + low24h: 10, + openToday: 11, + vwapToday: 12, + vwap24h: 13, + serverTimeUTC: "2018-01-01", + }; + return of(result); + } + + getTodayQuotes(currencypair: string): Observable { + return of([]); + } +} + +describe("IbdetailComponent", () => { + let component: IbdetailComponent; + let fixture: ComponentFixture; + const mockService = new MockService(null); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [IbdetailComponent], + imports: [RouterTestingModule, + BrowserModule, + FormsModule, + ReactiveFormsModule, + BrowserAnimationsModule, + MatToolbarModule, + MatRadioModule, + MatCheckboxModule, + NgxLineChartsModule], + providers: [{ provide: ItbitService, useValue: mockService }, provideHttpClient(withInterceptorsFromDi())] +}).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(IbdetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should have value", () => { + expect(component.currQuote.ask).toBe(3); + }); + it("should show lastPrice", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#lastPrice")).nativeElement; + expect(el.textContent).toEqual("5.00"); + }); + it("should show high24h", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#high24h")).nativeElement; + expect(el.textContent).toEqual("9.00"); + }); + it("should show low24h", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#low24h")).nativeElement; + expect(el.textContent).toEqual("10.00"); + }); + it("should show bid", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#bid")).nativeElement; + expect(el.textContent).toEqual("1.00"); + }); + it("should show ask", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#ask")).nativeElement; + expect(el.textContent).toEqual("3.00"); + }); + it("should show openToday", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#openToday")).nativeElement; + expect(el.textContent).toEqual("11.00"); + }); + it("should show vwap24h", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#vwap24h")).nativeElement; + expect(el.textContent).toEqual("13.00"); + }); + it("should show createdAt", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#createdAt")).nativeElement; + const myDate = new Date(component.currQuote.createdAt); + const dateStr = + (myDate.getMinutes().toString().length === 1 + ? "0" + myDate.getMinutes() + : myDate.getMinutes()) + + ":" + + (myDate.getSeconds().toString().length === 1 + ? "0" + myDate.getSeconds() + : myDate.getSeconds()); + expect(el.textContent.substr(3, el.textContent.length)).toEqual(dateStr); + }); + it("should show volume24", () => { + const de: DebugElement = fixture.debugElement; + const el: HTMLElement = de.query(By.css("#volume24")).nativeElement; + expect(el.textContent).toEqual("7.00"); + }); +}); diff --git a/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.ts b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.ts new file mode 100644 index 00000000..ce0bad77 --- /dev/null +++ b/frontend/src/angular/src/app/details/ibdetail/ibdetail.component.ts @@ -0,0 +1,129 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { + Component, + OnInit, + Inject, + LOCALE_ID, + DestroyRef, + inject, +} from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + trigger, + state, + animate, + transition, + style, +} from "@angular/animations"; +import { BehaviorSubject, Observable, repeat } from "rxjs"; +import { QuoteIb } from "../../common/quote-ib"; +import { ItbitService } from "../../services/itbit.service"; +import { DetailBase, Tuple } from "src/app/common/detail-base"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; + +@Component({ + selector: "app-ibdetail", + templateUrl: "./ibdetail.component.html", + styleUrls: ["./ibdetail.component.scss"], + animations: [ + trigger("showChart", [ + transition("false => true", [ + style({ opacity: 0 }), + animate(1000, style({ opacity: 1 })), + ]), + ]), + ], + standalone: false +}) +export class IbdetailComponent extends DetailBase implements OnInit { + public currQuote: QuoteIb; + protected chartShow = new BehaviorSubject(false); + protected todayQuotes: QuoteIb[] = []; + private readonly destroy: DestroyRef = inject(DestroyRef); + + constructor( + private route: ActivatedRoute, + private router: Router, + private serviceIb: ItbitService, + @Inject(LOCALE_ID) private myLocale: string, + ) { + super(myLocale); + } + + ngOnInit() { + this.chartShow.next(false); + this.route.params.subscribe((params) => { + this.currPair = params.currpair; + this.serviceIb + .getCurrentQuote(this.currPair) + .pipe(repeat({ delay: 10000 }), takeUntilDestroyed(this.destroy)) + .subscribe((quote) => (this.currQuote = quote)); + this.serviceIb + .getTodayQuotes(this.currPair) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe((quotes) => { + this.todayQuotes = quotes; + this.updateChartData( + quotes.map( + (quote) => + new Tuple(quote.createdAt, quote.lastPrice), + ), + ); + this.chartShow.next(true); + }); + }); + } + + back(): void { + this.router.navigate(["/"]); + } + + changeTf() { + this.chartShow.next(false); + const currpair = this.route.snapshot.paramMap.get("currpair"); + let quoteObserv: Observable; + if (this.timeframe === this.utils.MyTimeFrames.Day7) { + quoteObserv = this.serviceIb.get7DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day30) { + quoteObserv = this.serviceIb.get30DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day90) { + quoteObserv = this.serviceIb.get90DayQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day180) { + quoteObserv = this.serviceIb.get6MonthsQuotes(currpair); + } else if (this.timeframe === this.utils.MyTimeFrames.Day365) { + quoteObserv = this.serviceIb.get1YearQuotes(currpair); + } else { + quoteObserv = this.serviceIb.getTodayQuotes(currpair); + } + quoteObserv.pipe(takeUntilDestroyed(this.destroy)).subscribe((quotes) => { + this.todayQuotes = quotes; + this.updateChartData( + quotes.map( + (quote) => + new Tuple(quote.createdAt, quote.lastPrice), + ), + ); + this.chartShow.next(true); + }); + } + + showReport() { + const currpair = this.route.snapshot.paramMap.get("currpair"); + const url = "/itbit" + this.utils.createReportUrl(this.timeframe, currpair); + window.open(url); + } +} diff --git a/src/angular/trader/src/app/services/myuser.service.spec.ts b/frontend/src/angular/src/app/orderbooks/orderbooks-routing.module.ts similarity index 60% rename from src/angular/trader/src/app/services/myuser.service.spec.ts rename to frontend/src/angular/src/app/orderbooks/orderbooks-routing.module.ts index b92f4247..fab6d381 100644 --- a/src/angular/trader/src/app/services/myuser.service.spec.ts +++ b/frontend/src/angular/src/app/orderbooks/orderbooks-routing.module.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,18 +13,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TestBed, inject } from '@angular/core/testing'; +import { NgModule } from "@angular/core"; +import { Routes, RouterModule } from "@angular/router"; +import { OrderbooksComponent } from "./orderbooks/orderbooks.component"; -import { MyuserService } from './myuser.service'; +const routes: Routes = [ + { + path: "", + component: OrderbooksComponent, + }, +]; -describe('MyuserService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [MyuserService] - }); - }); - - it('should be created', inject([MyuserService], (service: MyuserService) => { - expect(service).toBeTruthy(); - })); -}); +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class OrderbooksRoutingModule {} diff --git a/frontend/src/angular/src/app/orderbooks/orderbooks.module.ts b/frontend/src/angular/src/app/orderbooks/orderbooks.module.ts new file mode 100644 index 00000000..147401a6 --- /dev/null +++ b/frontend/src/angular/src/app/orderbooks/orderbooks.module.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; + +import { OrderbooksRoutingModule } from "./orderbooks-routing.module"; +import { OrderbooksComponent } from "./orderbooks/orderbooks.component"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { MatButtonModule } from "@angular/material/button"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { MatInputModule } from "@angular/material/input"; +import { MatListModule } from "@angular/material/list"; +import { MatRadioModule } from "@angular/material/radio"; +import { MatSelectModule } from "@angular/material/select"; +import { MatToolbarModule } from "@angular/material/toolbar"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + MatToolbarModule, + MatSelectModule, + MatRadioModule, + MatInputModule, + MatCheckboxModule, + MatButtonModule, + MatListModule, + OrderbooksRoutingModule, + ], + declarations: [OrderbooksComponent], +}) +export class OrderbooksModule {} diff --git a/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.html b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.html new file mode 100644 index 00000000..547cf218 --- /dev/null +++ b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.html @@ -0,0 +1,167 @@ +
+ + Orderbooks + + + +
+
+
+
+
+ Bitstamp +
+
+ Itbit +
+
+ Bitfinex +
+
+
+ + Buy + Sell + +
+
+
+
+ + + @for (curr of currencies; track curr) { + + {{ curr.name }} + + } + + +
+
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+
+ + Bitstamp Orders + Price  -  Amount + @for (order of bsOrders; track order) { + + {{ order.price }} - {{ order.amount }} + + } + +
+
+ + Itbit Orders + Price  -  Amount + @for (order of ibOrders; track order) { + + {{ order.price }} - {{ order.amount }} + + } + +
+
+ + Bitfinex Orders + Price  -  Amount + @for (order of bfOrders; track order) { + + {{ order.price }} - {{ order.amount }} + + } + +
+
+
diff --git a/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.scss b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.scss new file mode 100644 index 00000000..0e069fe0 --- /dev/null +++ b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +.formatMs { + margin-top: 25px; +} + +.flexcontainer { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: space-around; +} + +.flexchild { + //margin-left: 10px; +} + +.example-fill-remaining-space { + // This fills the remaining space, by using flexbox. + // Every toolbar row uses a flexbox row layout. + flex: 1 1 auto; +} diff --git a/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.spec.ts b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.spec.ts new file mode 100644 index 00000000..86061335 --- /dev/null +++ b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.spec.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; + +import { OrderbooksComponent } from "./orderbooks.component"; + +//describe('OrderbooksComponent', () => { +// let component: OrderbooksComponent; +// let fixture: ComponentFixture; +// +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ OrderbooksComponent ] +// }) +// .compileComponents(); +// })); +// +// beforeEach(() => { +// fixture = TestBed.createComponent(OrderbooksComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); +// +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +//}); diff --git a/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.ts b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.ts new file mode 100644 index 00000000..47aa0bb7 --- /dev/null +++ b/frontend/src/angular/src/app/orderbooks/orderbooks/orderbooks.component.ts @@ -0,0 +1,189 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Component, DestroyRef, OnInit, inject } from "@angular/core"; +import { BitstampService } from "../../services/bitstamp.service"; +import { ItbitService } from "../../services/itbit.service"; +import { BitfinexService } from "../../services/bitfinex.service"; +import { Router } from "@angular/router"; +import { OrderbookBs } from "../../common/orderbook-bs"; +import { OrderbookBf, OrderBf } from "../../common/orderbook-bf"; +import { OrderbookIb } from "../../common/orderbook-ib"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; + +@Component({ + selector: "app-orderbooks", + templateUrl: "./orderbooks.component.html", + styleUrls: ["./orderbooks.component.scss"], + standalone: false +}) +export class OrderbooksComponent implements OnInit { + public currencies: MyCurr[]; + protected orderbookBs: OrderbookBs; + protected orderbookBf: OrderbookBf; + protected orderbookIb: OrderbookIb; + protected model = new MyModel(null, false, false, false, 1, null); + protected bsOrders: MyOrder[] = []; + protected bfOrders: MyOrder[] = []; + protected ibOrders: MyOrder[] = []; + private readonly destroy: DestroyRef = inject(DestroyRef); + + constructor( + private router: Router, + private serviceBs: BitstampService, + private serviceIb: ItbitService, + private serviceBf: BitfinexService, + ) {} + + ngOnInit() { + this.currencies = [ + new MyCurr(this.serviceBf.BTCUSD, "Btc - Usd"), + new MyCurr(this.serviceBf.ETHUSD, "Eth - Usd"), + new MyCurr(this.serviceBf.LTCUSD, "Ltc - Usd"), + new MyCurr(this.serviceBf.XRPUSD, "Xrp - Usd"), + ]; + console.log("hallo"); + } + onSubmit() { + //console.log( this.model ); + if (this.model.itbitCb && this.model.currpair === this.serviceBf.BTCUSD) { + this.serviceIb + .getOrderbook(this.serviceIb.BTCUSD) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe((ob) => { + // this.orderbookIb = ob; + this.ibOrders = this.filterObIb(ob); + }); + } else { + this.ibOrders = []; + } + if (this.model.bitstampCb) { + this.serviceBs + .getOrderbook(this.model.currpair) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe((ob) => { + // this.orderbookBs = ob; + this.bsOrders = this.filterObBs(ob); + }); + } else { + this.bsOrders = []; + } + if (this.model.bitfinexCb) { + this.serviceBf + .getOrderbook(this.model.currpair) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe((ob) => { + // this.orderbookBf = ob; + this.bfOrders = this.filterObBf(ob); + }); + } else { + this.bfOrders = []; + } + } + back() { + console.log("Back"); + this.router.navigate(["/overview"]); + } + + private filterObBs(ob: OrderbookBs): MyOrder[] { + const myOrders: MyOrder[] = []; + let sum = 0; + const bidAskArr = this.model.buysell === 1 ? ob.asks : ob.bids; + for (const order of bidAskArr) { + myOrders.push( + new MyOrder( + this.model.buysell, + parseFloat(order[0]), + parseFloat(order[1]), + sum > this.model.amount ? "black" : "blue", + ), + ); + sum += parseFloat(order[1]); + if (sum > this.model.amount * 1.5) { + break; + } + } + return myOrders; + } + + private filterObBf(ob: OrderbookBf): MyOrder[] { + const myOrders: MyOrder[] = []; + let sum = 0; + const bidAskArr = this.model.buysell === 1 ? ob.asks : ob.bids; + for (const order of bidAskArr) { + myOrders.push( + new MyOrder( + this.model.buysell, + parseFloat(order.price), + parseFloat(order.amount), + sum > this.model.amount ? "black" : "blue", + ), + ); + sum += parseFloat(order.amount); + if (sum > this.model.amount * 1.5) { + break; + } + } + return myOrders; + } + + private filterObIb(ob: OrderbookIb): MyOrder[] { + const myOrders: MyOrder[] = []; + let sum = 0; + const bidAskArr = this.model.buysell === 1 ? ob.asks : ob.bids; + for (const order of bidAskArr) { + myOrders.push( + new MyOrder( + this.model.buysell, + parseFloat(order[0]), + parseFloat(order[1]), + sum > this.model.amount ? "black" : "blue", + ), + ); + sum += parseFloat(order[1]); + if (sum > this.model.amount * 1.5) { + break; + } + } + return myOrders; + } +} + +export class MyOrder { + constructor( + public buysell: number, + public price: number, + public amount: number, + public color: string, + ) {} +} + +export class MyModel { + constructor( + public currpair: string, + public bitstampCb: boolean, + public itbitCb: boolean, + public bitfinexCb: boolean, + public buysell: number, + public amount: number, + ) {} +} + +export class MyCurr { + constructor( + public value: string, + public name: string, + ) {} +} diff --git a/frontend/src/angular/src/app/overview/login/login.component.html b/frontend/src/angular/src/app/overview/login/login.component.html new file mode 100644 index 00000000..860c1e17 --- /dev/null +++ b/frontend/src/angular/src/app/overview/login/login.component.html @@ -0,0 +1,154 @@ +
+ + + @if (waitingForResponse) { + + } +
+ @if (!waitingForResponse) { +
    +
  • + + + +
  • +
  • + + + +
  • + @if (loginFailed) { +
  • + Login Failed +
  • + } +
  • + + +
  • +
+ } +
+
+ + @if (waitingForResponse) { + + } +
+ @if (!waitingForResponse) { +
    +
  • + + + +
  • +
  • + + + + + + +
  • +
  • + + + +
  • + @if (signinFailed) { +
  • + Signin Failed +
  • + } +
  • + + +
  • +
+ } +
+
+
+
+ + +
+
+

Waiting...

+ +
+
+
diff --git a/frontend/src/angular/src/app/overview/login/login.component.scss b/frontend/src/angular/src/app/overview/login/login.component.scss new file mode 100644 index 00000000..f2ea566a --- /dev/null +++ b/frontend/src/angular/src/app/overview/login/login.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +ul { + list-style-type: none; +} + +.errorText { + color: red; +} + +.spinner-container { + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +::ng-deep { + .cdk-overlay-backdrop-showing { + backdrop-filter: blur(3px); + } +} diff --git a/frontend/src/angular/src/app/overview/login/login.component.spec.ts b/frontend/src/angular/src/app/overview/login/login.component.spec.ts new file mode 100644 index 00000000..93dd526c --- /dev/null +++ b/frontend/src/angular/src/app/overview/login/login.component.spec.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; + +import { LoginComponent } from "./login.component"; + +//describe('LoginComponent', () => { +// let component: LoginComponent; +// let fixture: ComponentFixture; +// +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ LoginComponent ] +// }) +// .compileComponents(); +// })); +// +// beforeEach(() => { +// fixture = TestBed.createComponent(LoginComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); +// +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +//}); diff --git a/frontend/src/angular/src/app/overview/login/login.component.ts b/frontend/src/angular/src/app/overview/login/login.component.ts new file mode 100644 index 00000000..b1b7f364 --- /dev/null +++ b/frontend/src/angular/src/app/overview/login/login.component.ts @@ -0,0 +1,178 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Component, OnInit, Inject, DestroyRef, inject } from "@angular/core"; +import { QuoteoverviewComponent } from "../quoteoverview/quoteoverview.component"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { MyuserService } from "../../services/myuser.service"; +import { MyUser } from "../../common/my-user"; +import { + FormGroup, + FormBuilder, + Validators, + AbstractControlOptions, +} from "@angular/forms"; +import { TokenService } from "ngx-simple-charts/base-service"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; + +enum FormFields { + username = "username", + password = "password", + password2 = "password2", + email = "email", +} + +@Component({ + selector: "app-login", + templateUrl: "./login.component.html", + styleUrls: ["./login.component.scss"], + standalone: false +}) +export class LoginComponent implements OnInit { + protected signinForm: FormGroup; + protected loginForm: FormGroup; + protected loginFailed = false; + protected signinFailed = false; + protected pwMatching = true; + protected formFields = FormFields; + protected waitingForResponse = false; + private user = new MyUser(); + private readonly destroy: DestroyRef = inject(DestroyRef); + + constructor( + public dialogRef: MatDialogRef, + private tokenService: TokenService, + @Inject(MAT_DIALOG_DATA) public data: any, + private myuserService: MyuserService, + fb: FormBuilder, + ) { + this.signinForm = fb.group( + { + [FormFields.username]: [ + "", + [Validators.required, Validators.minLength(4)], + ], + [FormFields.password]: [ + "", + [Validators.required, Validators.minLength(4)], + ], + [FormFields.password2]: [ + "", + [Validators.required, Validators.minLength(4)], + ], + [FormFields.email]: ["", Validators.required], + }, + { + validators: this.validate.bind(this), + } as AbstractControlOptions, + ); + this.loginForm = fb.group({ + [FormFields.username]: [ + "", + [Validators.required, Validators.minLength(4)], + ], + [FormFields.password]: [ + "", + [Validators.required, Validators.minLength(4)], + ], + }); + } + + ngOnInit() {} + + validate(group: FormGroup) { + if ( + group.get(FormFields.password).touched || + group.get(FormFields.password2).touched + ) { + this.pwMatching = + group.get(FormFields.password).value === + group.get(FormFields.password2).value && + group.get(FormFields.password).value !== ""; + if (!this.pwMatching) { + // eslint-disable-next-line @typescript-eslint/naming-convention + group.get(FormFields.password).setErrors({ MatchPassword: true }); + // eslint-disable-next-line @typescript-eslint/naming-convention + group.get(FormFields.password2).setErrors({ MatchPassword: true }); + } else { + group.get(FormFields.password).setErrors(null); + group.get(FormFields.password2).setErrors(null); + } + } + return this.pwMatching; + } + + onSigninClick(): void { + const myUser = new MyUser(); + myUser.userId = this.signinForm.get(FormFields.username).value; + myUser.password = this.signinForm.get(FormFields.password).value; + myUser.email = this.signinForm.get(FormFields.email).value; + // console.log(this.signinForm); + // console.log(myUser); + this.waitingForResponse = true; + this.myuserService + .postSignin(myUser) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe({ + next: (us) => this.signin(us), + error: (err) => console.log(err), + }); + } + + onLoginClick(): void { + const myUser = new MyUser(); + myUser.userId = this.loginForm.get(FormFields.username).value; + myUser.password = this.loginForm.get(FormFields.password).value; + // console.log(myUser); + this.waitingForResponse = true; + this.myuserService + .postLogin(myUser) + .pipe(takeUntilDestroyed(this.destroy)) + .subscribe({ + next: (us) => this.login(us), + error: (err) => console.log(err), + }); + } + + signin(us: MyUser): void { + this.user = us; + this.data.loggedIn = !!us?.token; + this.waitingForResponse = false; + if (this.user.userId !== null) { + this.signinFailed = false; + this.dialogRef.close(this.data.loggedIn); + } else { + this.signinFailed = true; + } + } + + login(us: MyUser): void { + this.user = us; + this.data.loggedIn = !!us?.token; + this.waitingForResponse = false; + if (this.user.userId !== null) { + this.loginFailed = false; + this.tokenService.token = us.token; + this.tokenService.userId = us.userId; + this.dialogRef.close(this.data.loggedIn); + } else { + this.loginFailed = true; + } + } + + onCancelClick(): void { + this.dialogRef.close(); + } +} diff --git a/frontend/src/angular/src/app/overview/overview-routing.module.ts b/frontend/src/angular/src/app/overview/overview-routing.module.ts new file mode 100644 index 00000000..376592a8 --- /dev/null +++ b/frontend/src/angular/src/app/overview/overview-routing.module.ts @@ -0,0 +1,31 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { NgModule } from "@angular/core"; +import { Routes, RouterModule } from "@angular/router"; +import { QuoteoverviewComponent } from "./quoteoverview/quoteoverview.component"; + +const routes: Routes = [ + { + path: "", + component: QuoteoverviewComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class OverviewRoutingModule {} diff --git a/frontend/src/angular/src/app/overview/overview.module.ts b/frontend/src/angular/src/app/overview/overview.module.ts new file mode 100644 index 00000000..af0902b7 --- /dev/null +++ b/frontend/src/angular/src/app/overview/overview.module.ts @@ -0,0 +1,50 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { MatButtonModule } from "@angular/material/button"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatTableModule } from "@angular/material/table"; +import { MatTabsModule } from "@angular/material/tabs"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { OverviewRoutingModule } from "./overview-routing.module"; +import { LoginComponent } from "./login/login.component"; +import { QuoteoverviewComponent } from "./quoteoverview/quoteoverview.component"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { LuxonDateModule } from "@angular/material-luxon-adapter"; + +@NgModule({ + imports: [ + CommonModule, + OverviewRoutingModule, + MatTableModule, + MatToolbarModule, + MatTabsModule, + MatButtonModule, + MatDialogModule, + MatFormFieldModule, + MatProgressSpinnerModule, + MatInputModule, + LuxonDateModule, + FormsModule, + ReactiveFormsModule, + ], + declarations: [LoginComponent, QuoteoverviewComponent], +}) +export class OverviewModule {} diff --git a/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.html b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.html new file mode 100644 index 00000000..dcd0dbc1 --- /dev/null +++ b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.html @@ -0,0 +1,130 @@ +
+ + Curreny Table + + + + @if (!loggedIn) { + + } + @if (loggedIn) { + + } + +
+ + + + Exchange + + + {{ element?.exchange }} + + + + + Currency Pair + + + {{ element?.currpair }} + + + + + Last + + +
+ {{ + !element?.last || element?.last < 0.01 + ? "--" + : (element?.last | number: "1.2") + }} +
+ +
+
+ + + Volume + + + {{ element?.volume < 1 ? "--" : (element?.volume | number: "1.2") }} + + + + +
+
+
diff --git a/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.scss b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.scss new file mode 100644 index 00000000..fb988549 --- /dev/null +++ b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.scss @@ -0,0 +1,60 @@ +/** + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +.popup { + display: none; +} + +.cell:hover { + .popup { + display: block; + width: 160px; + margin-top: 0px; + margin-left: 100px; + background: #ffffff; + border-radius: 6px; + padding: 8px 0; + position: absolute; + z-index: 1; + color: green; + border: solid 1px black; + } + color: green; + cursor: pointer; +} + +.cell-outdated { + color: #34a8eb; + &:hover { + .popup { + color: #34a8eb; + } + } +} + +.example-container { + display: flex; + flex-direction: column; +} + +.mat-mdc-table { + overflow: auto; +} + +.example-fill-remaining-space { + // This fills the remaining space, by using flexbox. + // Every toolbar row uses a flexbox row layout. + flex: 1 1 auto; +} diff --git a/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.spec.ts b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.spec.ts new file mode 100644 index 00000000..a4194c92 --- /dev/null +++ b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.spec.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; + +import { QuoteoverviewComponent } from "./quoteoverview.component"; + +//describe('QuoteoverviewComponent', () => { +// let component: QuoteoverviewComponent; +// let fixture: ComponentFixture; +// +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ QuoteoverviewComponent ] +// }) +// .compileComponents(); +// })); +// +// beforeEach(() => { +// fixture = TestBed.createComponent(QuoteoverviewComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); +// +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +//}); diff --git a/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.ts b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.ts new file mode 100644 index 00000000..a8a3828f --- /dev/null +++ b/frontend/src/angular/src/app/overview/quoteoverview/quoteoverview.component.ts @@ -0,0 +1,338 @@ +/* Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import { + Component, + OnInit, + OnDestroy, + DestroyRef, +} from "@angular/core"; +import { BitstampService } from "../../services/bitstamp.service"; +import { CoinbaseService } from "../../services/coinbase.service"; +import { ItbitService } from "../../services/itbit.service"; +import { BitfinexService } from "../../services/bitfinex.service"; +import { QuoteBs } from "../../common/quote-bs"; +import { QuoteCb } from "../../common/quote-cb"; +import { QuoteIb } from "../../common/quote-ib"; +import { QuoteBf } from "../../common/quote-bf"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { DataSource, CollectionViewer } from "@angular/cdk/collections"; +import { Router } from "@angular/router"; +import { CommonUtils } from "../../common/common-utils"; +import { MatDialog } from "@angular/material/dialog"; +import { LoginComponent } from "../login/login.component"; +import { MyuserService } from "../../services/myuser.service"; +import { filter } from "rxjs/operators"; +import { TokenService } from "ngx-simple-charts/base-service"; +import { DateTime, Duration } from "luxon"; + +@Component({ + selector: "app-quoteoverview", + templateUrl: "./quoteoverview.component.html", + styleUrls: ["./quoteoverview.component.scss"], + standalone: false +}) +export class QuoteoverviewComponent implements OnInit, OnDestroy { + protected datasource = new Myds(); + protected loggedIn = false; + private interval: any; + private utils = new CommonUtils(); + + constructor( + private router: Router, + private serviceBs: BitstampService, + private serviceCb: CoinbaseService, + private serviceIb: ItbitService, + private serviceBf: BitfinexService, + private serviceMu: MyuserService, + private tokenService: TokenService, + private destroy: DestroyRef, + private dialog: MatDialog, + ) {} + + ngOnInit() { + if (this.interval) { + clearInterval(this.interval); + } + this.interval = setInterval(() => { + this.refreshData(); + }, 15000); + if (this.datasource.rows.length < 16) { + for (let i = 0; i < 16; i++) { + this.datasource.rows.push( + new Myrow( + "", + "", + 0, + DateTime.now() + .minus(Duration.fromObject({ minutes: 5 })) + .toISO(), + 0, + null, + -1, + -1, + ), + ); + } + this.datasource.updateRows(); + } + this.refreshData(); + this.loggedIn = !!this.tokenService.token; + //console.log(this.hash); + } + + ngOnDestroy(): void { + if (this.interval) { + clearInterval(this.interval); + } + } + + openLoginDialog(): void { + const dialogRef = this.dialog.open(LoginComponent, { + width: "600px", + disableClose: true, + hasBackdrop: true, + data: { loggedIn: this.loggedIn }, + }); + + dialogRef.afterClosed().subscribe((result) => { + this.loggedIn = result; + }); + } + + isElementOutdated(element: Myrow): boolean { + const result = + DateTime.fromISO(element.createdAt).diffNow().toMillis() < -120000; + return result; + } + + logout(): void { + this.loggedIn = !this.serviceMu.postLogout(); + } + + orderbooks(): void { + this.router.navigateByUrl("/orderbooks"); + } + + statistics(): void { + this.router.navigateByUrl("/statistics"); + } + + selectedRow(row: Myrow): void { + //console.log(row); + if (row.exchange === "Bitstamp") { + this.router.navigateByUrl("details/bsdetail/" + row.pair); + } else if (row.exchange === "Itbit" && row.pair === "XBTUSD") { + this.router.navigateByUrl("details/ibdetail/" + this.serviceIb.BTCUSD); + } else if (row.exchange === "Coinbase") { + this.router.navigateByUrl("details/cbdetail/" + row.pair); + } else if (row.exchange === "Bitfinex") { + this.router.navigateByUrl("details/bfdetail/" + row.pair); + } + } + + createRowBs(quote: QuoteBs, exchange: string, currpair: string): Myrow { + return !quote + ? new Myrow(exchange, currpair) + : new Myrow( + exchange, + currpair, + this.formatNumber(quote?.last), + quote.createdAt, + this.formatNumber(quote?.volume), + quote?.pair, + this.formatNumber(quote?.high), + this.formatNumber(quote?.low), + ); + } + + createRowBf(quote: QuoteBf, exchange: string, currpair: string): Myrow { + return !quote + ? new Myrow(exchange, currpair) + : new Myrow( + exchange, + currpair, + this.formatNumber(quote?.last_price), + quote.createdAt, + this.formatNumber(quote?.volume), + quote?.pair, + this.formatNumber(quote?.high), + this.formatNumber(quote?.low), + ); + } + + private createCbRow(quote: QuoteCb, value: number, currPair: string): Myrow { + return new Myrow( + "Coinbase", + this.utils.getCurrpairName(currPair), + value, + quote.createdAt, + -1, + currPair, + -1, + -1, + ); + } + + createRowsCb(quote: QuoteCb): Myrow[] { + const rows: Myrow[] = []; + rows.push( + this.createCbRow( + quote, + !quote ? 0 : this.formatNumber(quote.usd), + this.serviceCb.BTCUSD, + ), + ); + rows.push( + this.createCbRow( + quote, + !quote ? 0 : this.formatNumber(quote.usd / quote.eth), + this.serviceCb.ETHUSD, + ), + ); + rows.push( + this.createCbRow( + quote, + !quote ? 0 : this.formatNumber(quote.usd / quote.ltc), + this.serviceCb.LTCUSD, + ), + ); + return rows; + } + + createRowIb(quote: QuoteIb, exchange: string, currpair: string): Myrow { + return !quote + ? new Myrow(exchange, currpair) + : new Myrow( + exchange, + currpair, + this.formatNumber(quote?.lastPrice), + quote.createdAt, + this.formatNumber(quote?.volume24h), + quote?.pair, + this.formatNumber(quote?.high24h), + this.formatNumber(quote?.low24h), + ); + } + + private refeshBsData(currPair: string, rowId: number): void { + this.serviceBs + .getCurrentQuote(currPair) + .pipe( + filter((result) => !!result?.last), + takeUntilDestroyed(this.destroy), + ) + .subscribe((quote) => { + this.datasource.rows[rowId] = this.createRowBs( + quote, + "Bitstamp", + this.utils.getCurrpairName(currPair), + ); + this.datasource.updateRows(); + }); + } + + private refreshBfData(currPair: string, rowId: number): void { + this.serviceBf + .getCurrentQuote(currPair) + .pipe( + filter((result) => !!result?.last_price), + takeUntilDestroyed(this.destroy), + ) + .subscribe((quote) => { + this.datasource.rows[rowId] = this.createRowBf( + quote, + "Bitfinex", + this.utils.getCurrpairName(currPair), + ); + this.datasource.updateRows(); + }); + } + + private refreshData(): void { + this.refeshBsData(this.serviceBs.BTCEUR, 0); + this.refeshBsData(this.serviceBs.ETHEUR, 1); + this.refeshBsData(this.serviceBs.LTCEUR, 2); + this.refeshBsData(this.serviceBs.XRPEUR, 3); + this.refeshBsData(this.serviceBs.BTCUSD, 4); + this.refeshBsData(this.serviceBs.ETHUSD, 5); + this.refeshBsData(this.serviceBs.LTCUSD, 6); + this.refeshBsData(this.serviceBs.XRPUSD, 7); + this.serviceIb + .getCurrentQuote(this.serviceIb.BTCUSD) + .pipe( + filter((result) => !!result?.lastPrice), + takeUntilDestroyed(this.destroy), + ) + .subscribe((quote) => { + this.datasource.rows[8] = this.createRowIb( + quote, + "Itbit", + this.utils.getCurrpairName(this.serviceIb.BTCUSD), + ); + this.datasource.updateRows(); + }); + this.serviceCb + .getCurrentQuote() + .pipe( + filter((result) => !!result?.btc), + takeUntilDestroyed(this.destroy), + ) + .subscribe((quote) => { + const myrows = this.createRowsCb(quote); + this.datasource.rows[9] = myrows[0]; + this.datasource.rows[10] = myrows[1]; + this.datasource.rows[11] = myrows[2]; + this.datasource.updateRows(); + }); + this.refreshBfData(this.serviceBf.BTCUSD, 12); + this.refreshBfData(this.serviceBf.ETHUSD, 13); + this.refreshBfData(this.serviceBf.LTCUSD, 14); + this.refreshBfData(this.serviceBf.XRPUSD, 15); + } + + private formatNumber(x: number): number { + return isNaN(x) ? 0 : Math.round(x * 100) / 100; + } +} + +export class Myds extends DataSource { + rows: Myrow[] = []; + private subject = new BehaviorSubject([]); + + updateRows(): void { + this.subject.next(this.rows); + } + + connect(collectionViewer: CollectionViewer): Observable { + return this.subject; + } + disconnect(collectionViewer: CollectionViewer): void { + this.subject.unsubscribe(); + } +} + +export class Myrow { + constructor( + public exchange: string = "", + public currpair: string = "", + public last: number = 0, + public createdAt: string = DateTime.now().toISO(), + public volume: number = 0, + public pair: string = "", + public high: number = 0, + public low: number = 0, + ) {} +} diff --git a/frontend/src/angular/src/app/services/auth-guard.service.spec.ts b/frontend/src/angular/src/app/services/auth-guard.service.spec.ts new file mode 100644 index 00000000..7cdf820d --- /dev/null +++ b/frontend/src/angular/src/app/services/auth-guard.service.spec.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { TestBed, inject } from "@angular/core/testing"; + +import { AuthGuardService } from "./auth-guard.service"; + +//describe('AuthGuardService', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [AuthGuardService] +// }); +// }); +// +// it('should be created', inject([AuthGuardService], (service: AuthGuardService) => { +// expect(service).toBeTruthy(); +// })); +//}); diff --git a/frontend/src/angular/src/app/services/auth-guard.service.ts b/frontend/src/angular/src/app/services/auth-guard.service.ts new file mode 100644 index 00000000..c4c197bb --- /dev/null +++ b/frontend/src/angular/src/app/services/auth-guard.service.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Injectable } from "@angular/core"; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; +import { Observable } from "rxjs"; +import { catchError, map, tap } from "rxjs/operators"; +import { MyuserService } from "./myuser.service"; +import { TokenService } from "ngx-simple-charts/base-service"; + +@Injectable({ providedIn: "root" }) +export class AuthGuardService { + constructor(private tokenService: TokenService) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + ): boolean | Observable | Promise { + return !!this.tokenService.token && !!this.tokenService.userId; + } +} diff --git a/src/angular/trader/src/app/services/auth-guard.service.spec.ts b/frontend/src/angular/src/app/services/bitfinex.service.spec.ts similarity index 59% rename from src/angular/trader/src/app/services/auth-guard.service.spec.ts rename to frontend/src/angular/src/app/services/bitfinex.service.spec.ts index 7d15056d..a100bc8e 100644 --- a/src/angular/trader/src/app/services/auth-guard.service.spec.ts +++ b/frontend/src/angular/src/app/services/bitfinex.service.spec.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,18 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TestBed, inject } from '@angular/core/testing'; +import { TestBed, inject } from "@angular/core/testing"; -import { AuthGuardService } from './auth-guard.service'; +import { BitfinexService } from "./bitfinex.service"; -describe('AuthGuardService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [AuthGuardService] - }); - }); - - it('should be created', inject([AuthGuardService], (service: AuthGuardService) => { - expect(service).toBeTruthy(); - })); -}); +//describe('BitfinexService', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [BitfinexService] +// }); +// }); +// +// it('should be created', inject([BitfinexService], (service: BitfinexService) => { +// expect(service).toBeTruthy(); +// })); +//}); diff --git a/frontend/src/angular/src/app/services/bitfinex.service.ts b/frontend/src/angular/src/app/services/bitfinex.service.ts new file mode 100644 index 00000000..61aba650 --- /dev/null +++ b/frontend/src/angular/src/app/services/bitfinex.service.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { catchError, map, tap } from "rxjs/operators"; +import { QuoteBf } from "../common/quote-bf"; +import { Utils } from "./utils"; +import { OrderbookBf } from "../common/orderbook-bf"; + +@Injectable({ providedIn: "root" }) +export class BitfinexService { + // eslint-disable-next-line @typescript-eslint/naming-convention + BTCUSD = "btcusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + ETHUSD = "ethusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + LTCUSD = "ltcusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + XRPUSD = "xrpusd"; + private reqOptionsArgs = { + headers: new HttpHeaders().set("Content-Type", "application/json"), + }; + private readonly bitfinex = "/bitfinex"; + + private utils = new Utils(); + + constructor(private http: HttpClient) {} + + getCurrentQuote(currencypair: string): Observable { + return this.http + .get( + this.bitfinex + "/" + currencypair + "/current", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("getCurrentQuote"))); + } + + getTodayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitfinex + "/" + currencypair + "/today", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("getTodayQuotes"))); + } + + get7DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitfinex + "/" + currencypair + "/7days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get7DayQuotes"))); + } + + get30DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitfinex + "/" + currencypair + "/30days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get30DayQuotes"))); + } + + get90DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitfinex + "/" + currencypair + "/90days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get90DayQuotes"))); + } + + get6MonthsQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitfinex + "/" + currencypair + "/6month", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get6MonthQuotes"))); + } + + get1YearQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitfinex + "/" + currencypair + "/1year", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get1YearQuotes"))); + } + + getOrderbook(currencypair: string): Observable { + const reqOptions = { headers: this.utils.createTokenHeader() }; + return this.http + .get( + this.bitfinex + "/" + currencypair + "/orderbook", + reqOptions, + ) + .pipe(catchError(this.utils.handleError("getOrderbook"))); + } +} diff --git a/frontend/src/angular/src/app/services/bitstamp.service.spec.ts b/frontend/src/angular/src/app/services/bitstamp.service.spec.ts new file mode 100644 index 00000000..878c56af --- /dev/null +++ b/frontend/src/angular/src/app/services/bitstamp.service.spec.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { TestBed, inject } from "@angular/core/testing"; + +import { BitstampService } from "./bitstamp.service"; + +//describe('BitstampserviceService', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [BitstampService] +// }); +// }); +// +// it('should be created', inject([BitstampService], (service: BitstampService) => { +// expect(service).toBeTruthy(); +// })); +//}); diff --git a/frontend/src/angular/src/app/services/bitstamp.service.ts b/frontend/src/angular/src/app/services/bitstamp.service.ts new file mode 100644 index 00000000..2fab2d75 --- /dev/null +++ b/frontend/src/angular/src/app/services/bitstamp.service.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { catchError } from "rxjs/operators"; +import { QuoteBs } from "../common/quote-bs"; +import { Utils } from "./utils"; +import { OrderbookBs } from "../common/orderbook-bs"; + +@Injectable({ providedIn: "root" }) +export class BitstampService { + // eslint-disable-next-line @typescript-eslint/naming-convention + BTCEUR = "btceur"; + // eslint-disable-next-line @typescript-eslint/naming-convention + ETHEUR = "etheur"; + // eslint-disable-next-line @typescript-eslint/naming-convention + LTCEUR = "ltceur"; + // eslint-disable-next-line @typescript-eslint/naming-convention + XRPEUR = "xrpeur"; + // eslint-disable-next-line @typescript-eslint/naming-convention + BTCUSD = "btcusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + ETHUSD = "ethusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + LTCUSD = "ltcusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + XRPUSD = "xrpusd"; + private reqOptionsArgs = { + headers: new HttpHeaders().set("Content-Type", "application/json"), + }; + private readonly bitstamp = "/bitstamp"; + private utils = new Utils(); + + constructor(private http: HttpClient) {} + + getCurrentQuote(currencypair: string): Observable { + return this.http + .get( + this.bitstamp + "/" + currencypair + "/current", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("getCurrentQuote"))); + } + + getTodayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitstamp + "/" + currencypair + "/today", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("getTodayQuotes"))); + } + + get7DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitstamp + "/" + currencypair + "/7days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get7DayQuotes"))); + } + + get30DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitstamp + "/" + currencypair + "/30days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get30DayQuotes"))); + } + + get90DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitstamp + "/" + currencypair + "/90days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get90DayQuotes"))); + } + + get6MonthsQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitstamp + "/" + currencypair + "/6month", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get6MonthsQuotes"))); + } + + get1YearQuotes(currencypair: string): Observable { + return this.http + .get( + this.bitstamp + "/" + currencypair + "/1year", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get1YearQuotes"))); + } + + getOrderbook(currencypair: string): Observable { + const reqOptions = { headers: this.utils.createTokenHeader() }; + return this.http + .get( + this.bitstamp + "/" + currencypair + "/orderbook", + reqOptions, + ) + .pipe(catchError(this.utils.handleError("getOrderbook"))); + } +} diff --git a/src/angular/trader/src/app/services/coinbase.service.spec.ts b/frontend/src/angular/src/app/services/coinbase.service.spec.ts similarity index 59% rename from src/angular/trader/src/app/services/coinbase.service.spec.ts rename to frontend/src/angular/src/app/services/coinbase.service.spec.ts index a54d91b8..37b923c4 100644 --- a/src/angular/trader/src/app/services/coinbase.service.spec.ts +++ b/frontend/src/angular/src/app/services/coinbase.service.spec.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,18 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TestBed, inject } from '@angular/core/testing'; +import { TestBed, inject } from "@angular/core/testing"; -import { CoinbaseService } from './coinbase.service'; +import { CoinbaseService } from "./coinbase.service"; -describe('CoinbaseService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [CoinbaseService] - }); - }); - - it('should be created', inject([CoinbaseService], (service: CoinbaseService) => { - expect(service).toBeTruthy(); - })); -}); +//describe('CoinbaseService', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [CoinbaseService] +// }); +// }); +// +// it('should be created', inject([CoinbaseService], (service: CoinbaseService) => { +// expect(service).toBeTruthy(); +// })); +//}); diff --git a/frontend/src/angular/src/app/services/coinbase.service.ts b/frontend/src/angular/src/app/services/coinbase.service.ts new file mode 100644 index 00000000..63b365a7 --- /dev/null +++ b/frontend/src/angular/src/app/services/coinbase.service.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { catchError, map, tap } from "rxjs/operators"; +import { QuoteCbSmall, QuoteCb } from "../common/quote-cb"; +import { Utils } from "./utils"; + +@Injectable({ providedIn: "root" }) +export class CoinbaseService { + // eslint-disable-next-line @typescript-eslint/naming-convention + BTCUSD = "btcusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + ETHUSD = "ethusd"; + // eslint-disable-next-line @typescript-eslint/naming-convention + LTCUSD = "ltcusd"; + private reqOptionsArgs = { + headers: new HttpHeaders().set("Content-Type", "application/json"), + }; + private readonly coinbase = "/coinbase"; + private utils = new Utils(); + + constructor(private http: HttpClient) {} + + getCurrentQuote(): Observable { + return this.http + .get(this.coinbase + "/current", this.reqOptionsArgs) + .pipe( + map((res) => this.lowercaseKeys(res as QuoteCb)), + catchError(this.utils.handleError("getCurrentQuote")), + ); + } + + getTodayQuotes(): Observable { + return this.http + .get(this.coinbase + "/today", this.reqOptionsArgs) + .pipe( + catchError(this.utils.handleError("getTodayQuotes")), + ); + } + + get7DayQuotes(): Observable { + return this.http + .get(this.coinbase + "/7days", this.reqOptionsArgs) + .pipe( + catchError(this.utils.handleError("get7DayQuotes")), + ); + } + + get30DayQuotes(): Observable { + return this.http + .get(this.coinbase + "/30days", this.reqOptionsArgs) + .pipe( + catchError(this.utils.handleError("get30DayQuotes")), + ); + } + + get90DayQuotes(): Observable { + return this.http + .get(this.coinbase + "/90days", this.reqOptionsArgs) + .pipe( + catchError(this.utils.handleError("get90DayQuotes")), + ); + } + + get6MonthsQuotes(): Observable { + return this.http + .get(this.coinbase + "/6month", this.reqOptionsArgs) + .pipe( + catchError(this.utils.handleError("get6MonthsQuotes")), + ); + } + + get1YearQuotes(): Observable { + return this.http + .get(this.coinbase + "/1year", this.reqOptionsArgs) + .pipe( + catchError(this.utils.handleError("get1YearQuotes")), + ); + } + + lowercaseKeys(quote: QuoteCb): QuoteCb { + for (const p in quote) { + if (quote.hasOwnProperty(p) && p !== "_id" && p !== "createdAt") { + quote[p.toLowerCase()] = quote[p]; + //console.log( p + " , " + quote[p] + "\n"); + //console.log( p.toLowerCase() + " , " + quote[p.toLowerCase()] + "\n"); + } + } + return quote; + } +} diff --git a/src/angular/trader/src/app/services/bitfinex.service.spec.ts b/frontend/src/angular/src/app/services/itbit.service.spec.ts similarity index 60% rename from src/angular/trader/src/app/services/bitfinex.service.spec.ts rename to frontend/src/angular/src/app/services/itbit.service.spec.ts index e871dff8..6dabd5b2 100644 --- a/src/angular/trader/src/app/services/bitfinex.service.spec.ts +++ b/frontend/src/angular/src/app/services/itbit.service.spec.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,18 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TestBed, inject } from '@angular/core/testing'; +import { TestBed, inject } from "@angular/core/testing"; -import { BitfinexService } from './bitfinex.service'; +import { ItbitService } from "./itbit.service"; -describe('BitfinexService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [BitfinexService] - }); - }); - - it('should be created', inject([BitfinexService], (service: BitfinexService) => { - expect(service).toBeTruthy(); - })); -}); +//describe('ItbitService', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [ItbitService] +// }); +// }); +// +// it('should be created', inject([ItbitService], (service: ItbitService) => { +// expect(service).toBeTruthy(); +// })); +//}); diff --git a/frontend/src/angular/src/app/services/itbit.service.ts b/frontend/src/angular/src/app/services/itbit.service.ts new file mode 100644 index 00000000..69c83661 --- /dev/null +++ b/frontend/src/angular/src/app/services/itbit.service.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { catchError } from "rxjs/operators"; +import { QuoteIb } from "../common/quote-ib"; +import { Utils } from "./utils"; +import { OrderbookIb } from "../common/orderbook-ib"; + +@Injectable({ providedIn: "root" }) +export class ItbitService { + // eslint-disable-next-line @typescript-eslint/naming-convention + BTCEUR = "btceur"; + // eslint-disable-next-line @typescript-eslint/naming-convention + BTCUSD = "btcusd"; + private reqOptionsArgs = { + headers: new HttpHeaders().set("Content-Type", "application/json"), + }; + private readonly itbit = "/itbit"; + private utils = new Utils(); + + constructor(private http: HttpClient) {} + + getCurrentQuote(currencypair: string): Observable { + return this.http + .get( + this.itbit + "/" + currencypair + "/current", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("getCurrentQuote"))); + } + + getTodayQuotes(currencypair: string): Observable { + return this.http + .get( + this.itbit + "/" + currencypair + "/today", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("getTodayQuotes"))); + } + + get7DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.itbit + "/" + currencypair + "/7days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get7DayQuotes"))); + } + + get30DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.itbit + "/" + currencypair + "/30days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get30DayQuotes"))); + } + + get90DayQuotes(currencypair: string): Observable { + return this.http + .get( + this.itbit + "/" + currencypair + "/90days", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get30DayQuotes"))); + } + + get6MonthsQuotes(currencypair: string): Observable { + return this.http + .get( + this.itbit + "/" + currencypair + "/6month", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get6MonthsQuotes"))); + } + + get1YearQuotes(currencypair: string): Observable { + return this.http + .get( + this.itbit + "/" + currencypair + "/1year", + this.reqOptionsArgs, + ) + .pipe(catchError(this.utils.handleError("get1YearQuotes"))); + } + + getOrderbook(currencypair: string): Observable { + const reqOptions = { headers: this.utils.createTokenHeader() }; + return this.http + .get( + this.itbit + "/" + currencypair + "/orderbook", + reqOptions, + ) + .pipe(catchError(this.utils.handleError("getOrderbook"))); + } +} diff --git a/src/angular/trader/src/app/services/bitstamp.service.spec.ts b/frontend/src/angular/src/app/services/myuser.service.spec.ts similarity index 59% rename from src/angular/trader/src/app/services/bitstamp.service.spec.ts rename to frontend/src/angular/src/app/services/myuser.service.spec.ts index 6cf7851f..2d480af8 100644 --- a/src/angular/trader/src/app/services/bitstamp.service.spec.ts +++ b/frontend/src/angular/src/app/services/myuser.service.spec.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,18 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TestBed, inject } from '@angular/core/testing'; +import { TestBed, inject } from "@angular/core/testing"; -import { BitstampService } from './bitstamp.service'; +import { MyuserService } from "./myuser.service"; -describe('BitstampserviceService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [BitstampService] - }); - }); - - it('should be created', inject([BitstampService], (service: BitstampService) => { - expect(service).toBeTruthy(); - })); -}); +//describe('MyuserService', () => { +// beforeEach(() => { +// TestBed.configureTestingModule({ +// providers: [MyuserService] +// }); +// }); +// +// it('should be created', inject([MyuserService], (service: MyuserService) => { +// expect(service).toBeTruthy(); +// })); +//}); diff --git a/frontend/src/angular/src/app/services/myuser.service.ts b/frontend/src/angular/src/app/services/myuser.service.ts new file mode 100644 index 00000000..db2318b1 --- /dev/null +++ b/frontend/src/angular/src/app/services/myuser.service.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders } from "@angular/common/http"; +import { Observable, of } from "rxjs"; +import { catchError, map, tap } from "rxjs/operators"; +import { MyUser } from "../common/my-user"; +import { Utils } from "./utils"; +import { AuthCheck } from "../common/authcheck"; +import { TokenService } from "ngx-simple-charts/base-service"; + +@Injectable({ providedIn: "root" }) +export class MyuserService { + private reqOptionsArgs = { + headers: new HttpHeaders().set("Content-Type", "application/json"), + }; + private utils = new Utils(); + private myUserUrl = "/myuser"; + + constructor( + private http: HttpClient, + private tokenService: TokenService, + ) {} + + postLogin(user: MyUser): Observable { + return this.http + .post(this.myUserUrl + "/login", user, this.reqOptionsArgs) + .pipe( + map((res) => { + const retval = res as MyUser; + this.tokenService.token = res.token; + this.tokenService.userId = res.userId; + return retval; + }), + catchError(this.utils.handleError("postLogin")), + ); + } + + postSignin(user: MyUser): Observable { + return this.http + .post(this.myUserUrl + "/signin", user, this.reqOptionsArgs) + .pipe( + map((res) => { + const retval = res as MyUser; + retval.salt = "xxx"; + retval.password = "yyy"; + return retval; + }), + catchError(this.utils.handleError("postSignin")), + ); + } + + postCheckAuthorisation(path: string): Observable { + const authcheck = new AuthCheck(); + authcheck.path = path; + const reqOptions = { headers: this.utils.createTokenHeader() }; + return this.http + .post(this.myUserUrl + "/authorize", authcheck, reqOptions) + .pipe( + catchError(this.utils.handleError("postCheckAuthorisation")), + ); + } + + postLogout(): boolean { + this.tokenService.logout(); + return true; + } +} diff --git a/src/angular/trader/src/app/services/itbit.service.spec.ts b/frontend/src/angular/src/app/services/statistic.service.spec.ts similarity index 64% rename from src/angular/trader/src/app/services/itbit.service.spec.ts rename to frontend/src/angular/src/app/services/statistic.service.spec.ts index 4b622339..384e36bf 100644 --- a/src/angular/trader/src/app/services/itbit.service.spec.ts +++ b/frontend/src/angular/src/app/services/statistic.service.spec.ts @@ -1,5 +1,5 @@ -/** - * Copyright 2016 Sven Loesekann +/* + Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,18 +13,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TestBed, inject } from '@angular/core/testing'; +import { TestBed } from "@angular/core/testing"; -import { ItbitService } from './itbit.service'; +import { StatisticService } from "./statistic.service"; + +/* +describe('StatisticService', () => { + let service: StatisticService; -describe('ItbitService', () => { beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ItbitService] - }); + TestBed.configureTestingModule({}); + service = TestBed.inject(StatisticService); }); - it('should be created', inject([ItbitService], (service: ItbitService) => { + it('should be created', () => { expect(service).toBeTruthy(); - })); + }); }); +*/ diff --git a/frontend/src/angular/src/app/services/statistic.service.ts b/frontend/src/angular/src/app/services/statistic.service.ts new file mode 100644 index 00000000..87e66780 --- /dev/null +++ b/frontend/src/angular/src/app/services/statistic.service.ts @@ -0,0 +1,50 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { + CommonStatistics, + StatisticCurrencyPair, + CoinExchange, +} from "../common/common-statistics"; +import { Utils } from "./utils"; +import { catchError } from "rxjs/operators"; + +@Injectable({ + providedIn: "root", +}) +export class StatisticService { + private readonly statistics = "/statistics"; + private utils = new Utils(); + + constructor(private http: HttpClient) {} + + getCommonStatistics( + currencypair: StatisticCurrencyPair, + coinExchange: CoinExchange, + ): Observable { + return this.http + .get( + `${this.statistics}/overview/${coinExchange}/${currencypair}`, + ) + .pipe( + catchError( + this.utils.handleError("getCommonStatistics"), + ), + ); + } +} diff --git a/frontend/src/angular/src/app/services/utils.ts b/frontend/src/angular/src/app/services/utils.ts new file mode 100644 index 00000000..1f8851c8 --- /dev/null +++ b/frontend/src/angular/src/app/services/utils.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Observable, of } from "rxjs"; +import { HttpHeaders } from "@angular/common/http"; + +export class Utils { + get token(): string { + return !localStorage.getItem("token") + ? null + : localStorage.getItem("token"); + } + + public createTokenHeader(): HttpHeaders { + let reqOptions = new HttpHeaders().set("Content-Type", "application/json"); + if (this.token) { + reqOptions = new HttpHeaders() + .set("Content-Type", "application/json") + .set("Authorization", "Bearer " + this.token); + } + return reqOptions; + } + + public handleError(operation = "operation", result?: T) { + return (error: any): Observable => { + // TODO: send the error to remote logging infrastructure + console.error(error); // log to console instead + + // TODO: better job of transforming error for user consumption + this.log(`${operation} failed: ${error.message}`); + + // Let the app keep running by returning an empty result. + return of(result as T); + }; + } + + /** Log a HeroService message with the MessageService */ + private log(message: string) { + console.log(message); + } +} diff --git a/frontend/src/angular/src/app/splash/splash.component.html b/frontend/src/angular/src/app/splash/splash.component.html new file mode 100644 index 00000000..36bd039d --- /dev/null +++ b/frontend/src/angular/src/app/splash/splash.component.html @@ -0,0 +1,14 @@ +
+
+ Welcome to AngularAndSpring +
+
+
+
+ +
+
diff --git a/src/angular/trader/src/app/common/myUser.ts b/frontend/src/angular/src/app/splash/splash.component.scss similarity index 76% rename from src/angular/trader/src/app/common/myUser.ts rename to frontend/src/angular/src/app/splash/splash.component.scss index fc1d569e..d23b2653 100644 --- a/src/angular/trader/src/app/common/myUser.ts +++ b/frontend/src/angular/src/app/splash/splash.component.scss @@ -13,14 +13,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -export class MyUser { - _id: string; - userId: string; - password: string; - salt: string; - email: string; - btcAmount: number; - ethAmount: number; - ltcAmount: number; - xrpAmount: number; +.container { + display: flex; + width: 100%; + justify-content: center; + align-content: center; +} +.content { + margin-top: 50px; +} +.example-margin { + margin: 0 10px; } diff --git a/frontend/src/angular/src/app/splash/splash.component.spec.ts b/frontend/src/angular/src/app/splash/splash.component.spec.ts new file mode 100644 index 00000000..84c1327e --- /dev/null +++ b/frontend/src/angular/src/app/splash/splash.component.spec.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; + +import { SplashComponent } from "./splash.component"; + +//describe('SplashComponent', () => { +// let component: SplashComponent; +// let fixture: ComponentFixture; +// +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ SplashComponent ] +// }) +// .compileComponents(); +// })); +// +// beforeEach(() => { +// fixture = TestBed.createComponent(SplashComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); +// +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +//}); diff --git a/frontend/src/angular/src/app/splash/splash.component.ts b/frontend/src/angular/src/app/splash/splash.component.ts new file mode 100644 index 00000000..52890253 --- /dev/null +++ b/frontend/src/angular/src/app/splash/splash.component.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Component, OnInit, AfterViewInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { + trigger, + state, + animate, + transition, + style, +} from "@angular/animations"; + +@Component({ + selector: "app-splash", + templateUrl: "./splash.component.html", + styleUrls: ["./splash.component.scss"], + animations: [ + trigger("showSplash", [ + state("true", style({ opacity: 1 })), + state("false", style({ opacity: 0 })), + transition("1 => 0", animate("750ms")), + transition("0 => 1", animate("750ms")), + ]), + ], + standalone: false +}) +export class SplashComponent implements OnInit, AfterViewInit { + protected myState = false; + + constructor(private router: Router) {} + + ngOnInit() { + this.router.navigateByUrl("overview"); + } + + ngAfterViewInit(): void { + setTimeout(() => (this.myState = true)); + } +} diff --git a/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.html b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.html new file mode 100644 index 00000000..c01cdacd --- /dev/null +++ b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.html @@ -0,0 +1,105 @@ +
+
+ + Bitcoin + Ether + Litecoin + Ripple + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DurationPerformanceVolatilityAvg. VolumeRange MinRange Max
1 Month{{ commonStatistics?.performance1Month | number: "1.2" }}{{ commonStatistics?.volatility1Month | number: "1.2" }}{{ commonStatistics?.avgVolume1Month | number: "1.2" }}{{ commonStatistics?.range1Month?.min | number: "1.2" }}{{ commonStatistics?.range1Month?.max | number: "1.2" }}
3 Months{{ commonStatistics?.performance3Month | number: "1.2" }}{{ commonStatistics?.volatility3Month | number: "1.2" }}{{ commonStatistics?.avgVolume3Month | number: "1.2" }}{{ commonStatistics?.range3Month?.min | number: "1.2" }}{{ commonStatistics?.range3Month?.max | number: "1.2" }}
6 Months{{ commonStatistics?.performance6Month | number: "1.2" }}{{ commonStatistics?.volatility6Month | number: "1.2" }}{{ commonStatistics?.avgVolume6Month | number: "1.2" }}{{ commonStatistics?.range6Month?.min | number: "1.2" }}{{ commonStatistics?.range6Month?.max | number: "1.2" }}
1 Year{{ commonStatistics?.performance1Year | number: "1.2" }}{{ commonStatistics?.volatility1Year | number: "1.2" }}{{ commonStatistics?.avgVolume1Year | number: "1.2" }}{{ commonStatistics?.range1Year?.min | number: "1.2" }}{{ commonStatistics?.range1Year?.max | number: "1.2" }}
2 Years{{ commonStatistics?.performance2Year | number: "1.2" }}{{ commonStatistics?.volatility2Year | number: "1.2" }}{{ commonStatistics?.avgVolume2Year | number: "1.2" }}{{ commonStatistics?.range2Year?.min | number: "1.2" }}{{ commonStatistics?.range2Year?.max | number: "1.2" }}
5 Years{{ commonStatistics?.performance5Year | number: "1.2" }}{{ commonStatistics?.volatility5Year | number: "1.2" }}{{ commonStatistics?.avgVolume5Year | number: "1.2" }}{{ commonStatistics?.range5Year?.min | number: "1.2" }}{{ commonStatistics?.range5Year?.max | number: "1.2" }}
+
+ @if (chartsLoading) { +
+ +
+ } + @if (!chartsLoading) { +
+ +
+ } +
diff --git a/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.scss b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.scss new file mode 100644 index 00000000..0524a91f --- /dev/null +++ b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.scss @@ -0,0 +1,58 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +.mat-mdc-radio-button ~ .mat-radio-button { + margin-left: 32px; +} + +.radio-wrapper { + margin-top: 12px; + display: flex; + justify-content: center; +} + +table { + margin-top: 16px; + width: 100%; +} + +td, +th { + text-align: left; + padding: 0 5px 0 5px; + border-bottom: solid 1px rgba(0, 0, 0, 0.1); + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 16px; + font-weight: 500; + color: rgba(0, 0, 0, 0.87); +} + +th { + font-weight: 600; +} + +.chart-container { + height: calc(100vh - 380px); + width: 100%; +} + +.center-div { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: calc(100vh - 380px); +} diff --git a/src/angular/trader/src/app/bfdetail/bfdetail.component.spec.ts b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.spec.ts similarity index 58% rename from src/angular/trader/src/app/bfdetail/bfdetail.component.spec.ts rename to frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.spec.ts index 7a791bbc..f73ad03a 100644 --- a/src/angular/trader/src/app/bfdetail/bfdetail.component.spec.ts +++ b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.spec.ts @@ -1,5 +1,5 @@ -/** - * Copyright 2016 Sven Loesekann +/* + Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,23 +13,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { BfdetailComponent } from './bfdetail.component'; +import { StatisticDetailsComponent } from "./statistic-details.component"; -describe('BfdetailComponent', () => { - let component: BfdetailComponent; - let fixture: ComponentFixture; +/* +describe('StatisticDetailsComponent', () => { + let component: StatisticDetailsComponent; + let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ BfdetailComponent ] + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ StatisticDetailsComponent ] }) .compileComponents(); - })); - beforeEach(() => { - fixture = TestBed.createComponent(BfdetailComponent); + fixture = TestBed.createComponent(StatisticDetailsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -38,3 +37,4 @@ describe('BfdetailComponent', () => { expect(component).toBeTruthy(); }); }); +*/ diff --git a/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.ts b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.ts new file mode 100644 index 00000000..b2b2e7f6 --- /dev/null +++ b/frontend/src/angular/src/app/statistics/statistic-details/statistic-details.component.ts @@ -0,0 +1,108 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Component, DestroyRef, Input, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ChartBars, ChartBar } from "ngx-simple-charts/bar"; +import { tap } from "rxjs"; +import { + CoinExchange, + CommonStatistics, + StatisticCurrencyPair, +} from "src/app/common/common-statistics"; +import { StatisticService } from "src/app/services/statistic.service"; + +@Component({ + selector: "app-statistic-details", + templateUrl: "./statistic-details.component.html", + styleUrls: ["./statistic-details.component.scss"], + standalone: false +}) +export class StatisticDetailsComponent implements OnInit { + @Input() + coinExchange: CoinExchange; + protected statisticCurrencyPair = StatisticCurrencyPair; + protected selCurrency = StatisticCurrencyPair.bcUsd; + protected commonStatistics = new CommonStatistics(); + protected chartBars!: ChartBars; + protected chartsLoading = true; + private myTabIndex = 0; + private readonly destroy: DestroyRef = inject(DestroyRef); + + constructor(private statisticService: StatisticService) {} + + get tabIndex() { + return this.myTabIndex; + } + + @Input() + set tabIndex(tabIndex: number) { + this.myTabIndex = tabIndex; + this.updateCurrency(); + } + + ngOnInit(): void { + this.statisticService + .getCommonStatistics(this.selCurrency, this.coinExchange) + .pipe( + tap((result) => (this.chartBars = this.createChartBars(result))), + takeUntilDestroyed(this.destroy), + ) + .subscribe((result) => (this.commonStatistics = result)); + } + + updateCurrency(): void { + if (!this.chartsLoading) { + this.chartsLoading = true; + this.statisticService + .getCommonStatistics(this.selCurrency, this.coinExchange) + .pipe( + tap((result) => (this.chartBars = this.createChartBars(result))), + takeUntilDestroyed(this.destroy), + ) + .subscribe((result) => (this.commonStatistics = result)); + } + } + + private createChartBars(commonStatistics: CommonStatistics): ChartBars { + const performanceValues = [ + { + x: $localize`:@@Month1:1 Month`, + y: commonStatistics.performance1Month, + }, + { + x: $localize`:@@Month3:3 Months`, + y: commonStatistics.performance3Month, + }, + { + x: $localize`:@@Month6:6 Months`, + y: commonStatistics.performance6Month, + }, + { x: $localize`:@@Year1:1 Year`, y: commonStatistics.performance1Year }, + { x: $localize`:@@Year2:2 Years`, y: commonStatistics.performance2Year }, + { x: $localize`:@@Year5:5 Years`, y: commonStatistics.performance5Year }, + ].reverse() as [ChartBar]; + const myChartBars = { + title: $localize`:@@statisticsPerformance:Performance`, + from: "", + xScaleHeight: 100, + yScaleWidth: 100, + chartBars: performanceValues, + } as ChartBars; + this.chartsLoading = false; + // console.log(myChartBars); + return myChartBars; + } +} diff --git a/frontend/src/angular/src/app/statistics/statistics-routing.module.ts b/frontend/src/angular/src/app/statistics/statistics-routing.module.ts new file mode 100644 index 00000000..cfc31f9e --- /dev/null +++ b/frontend/src/angular/src/app/statistics/statistics-routing.module.ts @@ -0,0 +1,31 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { NgModule } from "@angular/core"; +import { Routes, RouterModule } from "@angular/router"; +import { StatisticsComponent } from "./statistics.component"; + +const routes: Routes = [ + { + path: "", + component: StatisticsComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class StatisticsRoutingModule {} diff --git a/frontend/src/angular/src/app/statistics/statistics.component.html b/frontend/src/angular/src/app/statistics/statistics.component.html new file mode 100644 index 00000000..7656fc0c --- /dev/null +++ b/frontend/src/angular/src/app/statistics/statistics.component.html @@ -0,0 +1,27 @@ +
+ + Statistics + + + +
+ + + + + + + + +
+
diff --git a/src/angular/trader/src/app/app.component.ts b/frontend/src/angular/src/app/statistics/statistics.component.scss similarity index 74% rename from src/angular/trader/src/app/app.component.ts rename to frontend/src/angular/src/app/statistics/statistics.component.scss index 75fed24c..ed32ef68 100644 --- a/src/angular/trader/src/app/app.component.ts +++ b/frontend/src/angular/src/app/statistics/statistics.component.scss @@ -13,13 +13,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Component } from '@angular/core'; -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] -}) -export class AppComponent { - title = 'app'; +.mat-mdc-table { + overflow: auto; +} + +.example-fill-remaining-space { + // This fills the remaining space, by using flexbox. + // Every toolbar row uses a flexbox row layout. + flex: 1 1 auto; } diff --git a/src/angular/trader/src/app/login/login.component.spec.ts b/frontend/src/angular/src/app/statistics/statistics.component.spec.ts similarity index 61% rename from src/angular/trader/src/app/login/login.component.spec.ts rename to frontend/src/angular/src/app/statistics/statistics.component.spec.ts index 044b8ded..3947f524 100644 --- a/src/angular/trader/src/app/login/login.component.spec.ts +++ b/frontend/src/angular/src/app/statistics/statistics.component.spec.ts @@ -1,5 +1,5 @@ -/** - * Copyright 2016 Sven Loesekann +/* + Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,23 +13,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { LoginComponent } from './login.component'; +import { StatisticsComponent } from "./statistics.component"; -describe('LoginComponent', () => { - let component: LoginComponent; - let fixture: ComponentFixture; +/* +describe('StatisticsComponent', () => { + let component: StatisticsComponent; + let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ LoginComponent ] + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ StatisticsComponent ] }) .compileComponents(); - })); - beforeEach(() => { - fixture = TestBed.createComponent(LoginComponent); + fixture = TestBed.createComponent(StatisticsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -38,3 +37,4 @@ describe('LoginComponent', () => { expect(component).toBeTruthy(); }); }); +*/ diff --git a/frontend/src/angular/src/app/statistics/statistics.component.ts b/frontend/src/angular/src/app/statistics/statistics.component.ts new file mode 100644 index 00000000..51bba47b --- /dev/null +++ b/frontend/src/angular/src/app/statistics/statistics.component.ts @@ -0,0 +1,40 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { Component, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { CoinExchange, CommonStatistics } from "../common/common-statistics"; + +@Component({ + selector: "app-statistics", + templateUrl: "./statistics.component.html", + styleUrls: ["./statistics.component.scss"], + standalone: false +}) +export class StatisticsComponent { + protected commonStatistics = new CommonStatistics(); + protected coinExchange = CoinExchange; + protected tabIndex = 0; + + constructor(private router: Router) {} + + back(): void { + this.router.navigate(["/"]); + } + + onSelTabChange(event: any): void { + this.tabIndex = event.index; + } +} diff --git a/frontend/src/angular/src/app/statistics/statistics.module.ts b/frontend/src/angular/src/app/statistics/statistics.module.ts new file mode 100644 index 00000000..081b6b38 --- /dev/null +++ b/frontend/src/angular/src/app/statistics/statistics.module.ts @@ -0,0 +1,44 @@ +/* + Copyright 2016 Sven Loesekann + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { StatisticsComponent } from "./statistics.component"; +import { StatisticsRoutingModule } from "./statistics-routing.module"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { MatButtonModule } from "@angular/material/button"; +import { MatTabsModule } from "@angular/material/tabs"; +import { MatRadioModule } from "@angular/material/radio"; +import { StatisticDetailsComponent } from "./statistic-details/statistic-details.component"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { NgxBarChartsModule } from "ngx-simple-charts/bar"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; + +@NgModule({ + declarations: [StatisticsComponent, StatisticDetailsComponent], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + StatisticsRoutingModule, + MatToolbarModule, + MatButtonModule, + MatTabsModule, + MatRadioModule, + NgxBarChartsModule, + MatProgressSpinnerModule, + ], +}) +export class StatisticsModule {} diff --git a/src/angular/trader/src/assets/.gitkeep b/frontend/src/angular/src/assets/.gitkeep similarity index 100% rename from src/angular/trader/src/assets/.gitkeep rename to frontend/src/angular/src/assets/.gitkeep diff --git a/src/angular/trader/src/environments/environment.prod.ts b/frontend/src/angular/src/environments/environment.prod.ts similarity index 61% rename from src/angular/trader/src/environments/environment.prod.ts rename to frontend/src/angular/src/environments/environment.prod.ts index 3612073b..c9669790 100644 --- a/src/angular/trader/src/environments/environment.prod.ts +++ b/frontend/src/angular/src/environments/environment.prod.ts @@ -1,3 +1,3 @@ export const environment = { - production: true + production: true, }; diff --git a/src/angular/trader/src/environments/environment.ts b/frontend/src/angular/src/environments/environment.ts similarity index 94% rename from src/angular/trader/src/environments/environment.ts rename to frontend/src/angular/src/environments/environment.ts index b7f639ae..cf6bba0d 100644 --- a/src/angular/trader/src/environments/environment.ts +++ b/frontend/src/angular/src/environments/environment.ts @@ -4,5 +4,5 @@ // The list of which env maps to which file can be found in `.angular-cli.json`. export const environment = { - production: false + production: false, }; diff --git a/src/angular/trader/src/favicon.ico b/frontend/src/angular/src/favicon.ico similarity index 100% rename from src/angular/trader/src/favicon.ico rename to frontend/src/angular/src/favicon.ico diff --git a/frontend/src/angular/src/index.html b/frontend/src/angular/src/index.html new file mode 100644 index 00000000..813d2acc --- /dev/null +++ b/frontend/src/angular/src/index.html @@ -0,0 +1,14 @@ + + + + + Trader + + + + + + + + + diff --git a/src/angular/trader/src/main.ts b/frontend/src/angular/src/main.ts similarity index 66% rename from src/angular/trader/src/main.ts rename to frontend/src/angular/src/main.ts index 771f123c..3c8c63d6 100644 --- a/src/angular/trader/src/main.ts +++ b/frontend/src/angular/src/main.ts @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016 Sven Loesekann Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,15 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { enableProdMode } from "@angular/core"; +import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; +import { AppModule } from "./app/app.module"; +import { environment } from "./environments/environment"; if (environment.production) { enableProdMode(); } -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.log(err)); +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.log(err)); diff --git a/frontend/src/angular/src/polyfills.ts b/frontend/src/angular/src/polyfills.ts new file mode 100644 index 00000000..c20e8bea --- /dev/null +++ b/frontend/src/angular/src/polyfills.ts @@ -0,0 +1,56 @@ +/*************************************************************************************************** + * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. + */ +import "@angular/localize/init"; +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import "zone.js"; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/frontend/src/angular/src/styles.scss b/frontend/src/angular/src/styles.scss new file mode 100644 index 00000000..fc7a0eba --- /dev/null +++ b/frontend/src/angular/src/styles.scss @@ -0,0 +1,12 @@ +/* You can add global styles to this file, and also import other style files */ + +@use "material-icons/iconfont/material-icons.scss"; + +.detailLinks { + margin-top: 10px; + margin-bottom: 10px; +} + +.currPair { + padding-left: 10px; +} diff --git a/frontend/src/angular/src/test.ts b/frontend/src/angular/src/test.ts new file mode 100644 index 00000000..4579da83 --- /dev/null +++ b/frontend/src/angular/src/test.ts @@ -0,0 +1,17 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import "zone.js/testing"; +import { getTestBed } from "@angular/core/testing"; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} from "@angular/platform-browser-dynamic/testing"; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + { + teardown: { destroyAfterEach: false }, + }, +); diff --git a/src/angular/trader/src/typings.d.ts b/frontend/src/angular/src/typings.d.ts similarity index 100% rename from src/angular/trader/src/typings.d.ts rename to frontend/src/angular/src/typings.d.ts diff --git a/frontend/src/angular/tsconfig.app.json b/frontend/src/angular/tsconfig.app.json new file mode 100644 index 00000000..58c0f980 --- /dev/null +++ b/frontend/src/angular/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} \ No newline at end of file diff --git a/frontend/src/angular/tsconfig.json b/frontend/src/angular/tsconfig.json new file mode 100644 index 00000000..d5673a37 --- /dev/null +++ b/frontend/src/angular/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "module": "esnext", + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2018", + "dom" + ], + "useDefineForClassFields": false + }, + "angularCompilerOptions": { + "strictTemplates": true, + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true, + "noImplicitAny": false + } +} \ No newline at end of file diff --git a/frontend/src/angular/tsconfig.spec.json b/frontend/src/angular/tsconfig.spec.json new file mode 100644 index 00000000..b5bda534 --- /dev/null +++ b/frontend/src/angular/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} \ No newline at end of file diff --git a/frontend/src/angular/tslint.json b/frontend/src/angular/tslint.json new file mode 100644 index 00000000..58a97b08 --- /dev/null +++ b/frontend/src/angular/tslint.json @@ -0,0 +1,155 @@ +{ + "extends": "tslint:recommended", + "rules": { + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, + "arrow-parens": false, + "arrow-return-shorthand": true, + "curly": true, + "deprecation": { + "severity": "warning" + }, + "component-class-suffix": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, + "interface-name": false, + "max-classes-per-file": false, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-empty": false, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], + "object-literal-sort-keys": false, + "ordered-imports": false, + "quotemark": [ + true, + "single" + ], + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" + } + }, + "trailing-comma": false, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true + , "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + } +}, + "rulesDirectory": [ + "codelyzer" + ] +} \ No newline at end of file diff --git a/minikube/angularandspring/.helmignore b/minikube/angularandspring/.helmignore new file mode 100644 index 00000000..f0c13194 --- /dev/null +++ b/minikube/angularandspring/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/minikube/angularandspring/Chart.yaml b/minikube/angularandspring/Chart.yaml new file mode 100644 index 00000000..398310ca --- /dev/null +++ b/minikube/angularandspring/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +appVersion: "1.0" +description: "AngularAndSpring Config" +name: angularandspring +version: 0.1.0 +icon: "https://angular.io/assets/images/logos/angular/angular.png" \ No newline at end of file diff --git a/minikube/angularandspring/templates/_helpers.tpl b/minikube/angularandspring/templates/_helpers.tpl new file mode 100644 index 00000000..c9ec61e4 --- /dev/null +++ b/minikube/angularandspring/templates/_helpers.tpl @@ -0,0 +1,50 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "helm-chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "helm-chart.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "helm-chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create envApp values +*/}} +{{- define "helpers.list-envApp-variables"}} +{{- $secretName := .Values.secret.name -}} +{{- range $key, $val := .Values.envApp.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: {{ $key }} +{{- end}} +{{- range $key, $val := .Values.envApp.normal }} +- name: {{ $key }} + value: {{ $val | quote }} +{{- end}} +{{- end }} \ No newline at end of file diff --git a/minikube/angularandspring/templates/kubTemplate.yaml b/minikube/angularandspring/templates/kubTemplate.yaml new file mode 100644 index 00000000..a421bcb1 --- /dev/null +++ b/minikube/angularandspring/templates/kubTemplate.yaml @@ -0,0 +1,146 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.secret.name }} +type: Opaque +data: + {{- range $key, $val := .Values.envApp.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ .Values.persistentVolumeName }} + labels: + type: local +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 5Gi + persistentVolumeReclaimPolicy: Retain + hostPath: + path: /data/mongo1 + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Values.volumeClaimName }} + labels: + app: mongopv +spec: + storageClassName: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.dbName }} + labels: + appdb: {{ .Values.dbName }} +spec: + replicas: 1 + selector: + matchLabels: + appdb: {{ .Values.dbName }} + template: + metadata: + labels: + appdb: {{ .Values.dbName }} + spec: + containers: + - name: {{ .Values.dbName }} + image: "{{ .Values.dbImageName }}:{{ .Values.dbImageVersion }}" + resources: + limits: + memory: "3G" + cpu: "0.6" + requests: + memory: "1G" + cpu: "0.3" + ports: + - containerPort: 27017 + volumeMounts: + - name: hostvol + mountPath: /data/db + volumes: + - name: hostvol + persistentVolumeClaim: + claimName: {{ .Values.volumeClaimName }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.dbName }} + labels: + app: {{ .Values.dbName }} +spec: + ports: + - port: 27017 + protocol: TCP + selector: + appdb: {{ .Values.dbName }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.webAppName }} + labels: + app: {{ .Values.webAppName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.webAppName }} + template: + metadata: + labels: + app: {{ .Values.webAppName }} + spec: + containers: + - name: {{ .Values.webAppName }} + image: "{{ .Values.webImageName }}:{{ .Values.webImageVersion }}" + imagePullPolicy: Always + resources: + limits: + memory: "768M" + cpu: "1.4" + requests: + memory: "256M" + cpu: "0.5" + env: + {{- include "helpers.list-envApp-variables" . | indent 10 }} + ports: + - containerPort: 8080 + livenessProbe: + httpGet: + path: "/actuator/health/livenessState" + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + startupProbe: + httpGet: + path: "/actuator/health/readinessState" + port: 8080 + failureThreshold: 60 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.webAppName }} + labels: + run: {{ .Values.webAppName }} +spec: + type: NodePort + ports: + - port: 8080 + protocol: TCP + selector: + app: {{ .Values.webAppName }} diff --git a/minikube/angularandspring/values.yaml b/minikube/angularandspring/values.yaml new file mode 100644 index 00000000..8842d40f --- /dev/null +++ b/minikube/angularandspring/values.yaml @@ -0,0 +1,19 @@ +webAppName: angularandspring +dbName: mongodb +webImageName: angular2guy/angularandspring +webImageVersion: latest +dbImageName: mongo +dbImageVersion: 4.4 +volumeClaimName: mongo-pv-claim +persistentVolumeName: mongo-pv-volume + +secret: + name: app-env-secret + +envApp: + normal: + MONGODB_HOST: mongodb + CPU_CONSTRAINT: true + SHUTDOWN_PHASE: 10s + secret: + JWTTOKEN_SECRET: secret-key1234567890abcdefghijklmnopqrstuvwxyz \ No newline at end of file diff --git a/minikube/angularandspringwithkafka/.helmignore b/minikube/angularandspringwithkafka/.helmignore new file mode 100755 index 00000000..f0c13194 --- /dev/null +++ b/minikube/angularandspringwithkafka/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/minikube/angularandspringwithkafka/Chart.yaml b/minikube/angularandspringwithkafka/Chart.yaml new file mode 100755 index 00000000..afd398db --- /dev/null +++ b/minikube/angularandspringwithkafka/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +appVersion: "1.1" +description: "AngularAndSpring with Kafka Config" +name: angularandspringwithkafka +version: 0.1.1 +icon: "https://angular.io/assets/images/logos/angular/angular.png" \ No newline at end of file diff --git a/minikube/angularandspringwithkafka/templates/_helpers.tpl b/minikube/angularandspringwithkafka/templates/_helpers.tpl new file mode 100755 index 00000000..66c9477d --- /dev/null +++ b/minikube/angularandspringwithkafka/templates/_helpers.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "helm-chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "helm-chart.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "helm-chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create envApp values +*/}} +{{- define "helpers.list-envApp-variables"}} +{{- $secretName := .Values.secret.name -}} +{{- range $key, $val := .Values.envApp.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: {{ $key }} +{{- end}} +{{- range $key, $val := .Values.envApp.normal }} +- name: {{ $key }} + value: {{ $val | quote }} +{{- end}} +{{- end }} + +{{/* +Create envKafka values +*/}} +{{- define "helpers.list-envKafkaApp-variables"}} +{{- $secretName := .Values.secret.nameKafka -}} +{{- range $key, $val := .Values.envKafka.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: {{ $key }} +{{- end}} +{{- range $key, $val := .Values.envKafka.normal }} +- name: {{ $key }} + value: {{ $val | quote }} +{{- end}} +{{- end }} \ No newline at end of file diff --git a/minikube/angularandspringwithkafka/templates/kubTemplate.yaml b/minikube/angularandspringwithkafka/templates/kubTemplate.yaml new file mode 100755 index 00000000..6ac120c9 --- /dev/null +++ b/minikube/angularandspringwithkafka/templates/kubTemplate.yaml @@ -0,0 +1,219 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.secret.name }} +type: Opaque +data: + {{- range $key, $val := .Values.envApp.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.secret.nameKafka }} +type: Opaque +data: + {{- range $key, $val := .Values.envKafka.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ .Values.persistentVolumeName }} + labels: + type: local +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 5Gi + persistentVolumeReclaimPolicy: Retain + hostPath: + path: /data/mongo1 + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Values.volumeClaimName }} + labels: + app: mongopv +spec: + storageClassName: "" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.kafkaName }} + labels: + app: {{ .Values.kafkaName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.kafkaName }} + template: + metadata: + labels: + app: {{ .Values.kafkaName }} + spec: + securityContext: + runAsUser: 0 + runAsGroup: 0 + fsGroup: 0 + containers: + - name: {{ .Values.kafkaName }} + image: "{{ .Values.kafkaImageName }}:{{ .Values.kafkaImageVersion }}" + imagePullPolicy: Always + resources: + limits: + memory: "1G" + cpu: "1.5" + requests: + memory: "768M" + cpu: "1.0" + env: + {{- include "helpers.list-envKafkaApp-variables" . | indent 10 }} + ports: + - containerPort: 9092 +# volumeMounts: +# - name: hostvol +# mountPath: /bitnami/kafka +# volumes: +# - name: hostvol +# persistentVolumeClaim: +# claimName: {{ .Values.volumeClaimName }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.kafkaServiceName }} + labels: + app: {{ .Values.kafkaServiceName }} +spec: +# type: NodePort + ports: + - name: tcp-client + port: 9092 + protocol: TCP + - name: tcp-interbroker + port: 9093 + protocol: TCP + targetPort: 9093 + selector: + app: {{ .Values.kafkaName }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.dbName }} + labels: + app: {{ .Values.dbName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.dbName }} + template: + metadata: + labels: + app: {{ .Values.dbName }} + spec: + containers: + - name: {{ .Values.dbName }} + image: "{{ .Values.dbImageName }}:{{ .Values.dbImageVersion }}" + resources: + limits: + memory: "3G" + cpu: "0.6" + requests: + memory: "1G" + cpu: "0.3" + ports: + - containerPort: 27017 + volumeMounts: + - name: hostvol + mountPath: /data/db + volumes: + - name: hostvol + persistentVolumeClaim: + claimName: {{ .Values.volumeClaimName }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.dbServiceName }} + labels: + app: {{ .Values.dbServiceName }} +spec: + ports: + - port: 27017 + protocol: TCP + selector: + app: {{ .Values.dbName }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.webAppName }} + labels: + app: {{ .Values.webAppName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.webAppName }} + template: + metadata: + labels: + app: {{ .Values.webAppName }} + spec: + containers: + - name: {{ .Values.webAppName }} + image: "{{ .Values.webImageName }}:{{ .Values.webImageVersion }}" + imagePullPolicy: Always + resources: + limits: + memory: "768M" + cpu: "1.4" + requests: + memory: "256M" + cpu: "0.5" + env: + {{- include "helpers.list-envApp-variables" . | indent 10 }} + ports: + - containerPort: 8080 + livenessProbe: + httpGet: + path: "/actuator/health" + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + startupProbe: + httpGet: + path: "/actuator/health/readinessState" + port: 8080 + failureThreshold: 60 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.webAppServiceName }} + labels: + run: {{ .Values.webAppServiceName }} +spec: + type: NodePort + ports: + - port: 8080 + nodePort: 8080 + protocol: TCP + selector: + app: {{ .Values.webAppName }} diff --git a/minikube/angularandspringwithkafka/values.yaml b/minikube/angularandspringwithkafka/values.yaml new file mode 100755 index 00000000..fede1c19 --- /dev/null +++ b/minikube/angularandspringwithkafka/values.yaml @@ -0,0 +1,41 @@ +webAppName: angularandspring +dbName: mongodb +webImageName: angular2guy/angularandspring +webImageVersion: latest +dbImageName: mongo +dbImageVersion: 4.4 +volumeClaimName: mongo-pv-claim +persistentVolumeName: mongo-pv-volume + +kafkaName: kafkaapp +zookeeperName: zookeeperserver +kafkaImageName: bitnami/kafka +kafkaImageVersion: latest +zookeeperImageName: bitnami/zookeeper +zookeeperImageVersion: latest +kafkaServiceName: kafkaservice +zookeeperServiceName: zookeeperservice +dbServiceName: mongodbservice +webAppServiceName: angularandspringservice + +secret: + name: app-env-secret + nameKafka: kafka-env-secret + +envApp: + normal: + MONGODB_HOST: mongodbservice + CPU_CONSTRAINT: true + SPRING_PROFILES_ACTIVE: prod + KAFKA_SERVICE_NAME: kafkaService + secret: + JWTTOKEN_SECRET: secret-key1234567890abcdefghijklmnopqrstuvwxyz + +envKafka: + normal: + KAFKA_CFG_NODE_ID: 0 + KAFKA_CFG_PROCESS_ROLES: controller,broker + KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 + KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafkaservice:9093 + KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER diff --git a/minikube/helmCommand.sh b/minikube/helmCommand.sh new file mode 100644 index 00000000..9a489cc9 --- /dev/null +++ b/minikube/helmCommand.sh @@ -0,0 +1,4 @@ +#!/bin/sh +#helm install angularandspring ./angularandspring --set serviceType=NodePort +#helm install kafka ./kafka +#helm install angularandspringwithkafka ./angularandspringwithkafka \ No newline at end of file diff --git a/minikube/kafka/.helmignore b/minikube/kafka/.helmignore new file mode 100644 index 00000000..c13e3c8f --- /dev/null +++ b/minikube/kafka/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj \ No newline at end of file diff --git a/minikube/kafka/Chart.yaml b/minikube/kafka/Chart.yaml new file mode 100644 index 00000000..7d66f6f8 --- /dev/null +++ b/minikube/kafka/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +appVersion: "1.0" +description: Kafka Development Setup +name: kafkadev +version: 0.1.0 +icon: "https://angular.io/assets/images/logos/angular/angular.png" \ No newline at end of file diff --git a/minikube/kafka/templates/_helpers.tpl b/minikube/kafka/templates/_helpers.tpl new file mode 100644 index 00000000..099401d8 --- /dev/null +++ b/minikube/kafka/templates/_helpers.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "helm-chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "helm-chart.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "helm-chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create envZookeeper values +*/}} +{{- define "helpers.list-envZookeeperApp-variables"}} +{{- $secretName := .Values.secret.nameZookeeper -}} +{{- range $key, $val := .Values.envZookeeper.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: {{ $key }} +{{- end}} +{{- range $key, $val := .Values.envZookeeper.normal }} +- name: {{ $key }} + value: {{ $val | quote }} +{{- end}} +{{- end }} + +{{/* +Create envKafka values +*/}} +{{- define "helpers.list-envKafkaApp-variables"}} +{{- $secretName := .Values.secret.nameKafka -}} +{{- range $key, $val := .Values.envKafka.secret }} +- name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: {{ $key }} +{{- end}} +{{- range $key, $val := .Values.envKafka.normal }} +- name: {{ $key }} + value: {{ $val | quote }} +{{- end}} +{{- end }} \ No newline at end of file diff --git a/minikube/kafka/templates/kubTemplate.yaml b/minikube/kafka/templates/kubTemplate.yaml new file mode 100644 index 00000000..3eae65ce --- /dev/null +++ b/minikube/kafka/templates/kubTemplate.yaml @@ -0,0 +1,121 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.secret.nameZookeeper }} +type: Opaque +data: + {{- range $key, $val := .Values.envZookeeper.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.secret.nameKafka }} +type: Opaque +data: + {{- range $key, $val := .Values.envKafka.secret }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.zookeeperName }} + labels: + app: {{ .Values.zookeeperName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.zookeeperName }} + template: + metadata: + labels: + app: {{ .Values.zookeeperName }} + spec: + containers: + - name: {{ .Values.zookeeperName }} + image: "{{ .Values.zookeeperImageName }}:{{ .Values.zookeeperImageVersion }}" + resources: + limits: + memory: "768M" + cpu: "0.5" + requests: + memory: "512M" + cpu: "0.5" + env: + {{- include "helpers.list-envZookeeperApp-variables" . | indent 10 }} + ports: + - containerPort: 2181 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.zookeeperServiceName }} + labels: + app: {{ .Values.zookeeperServiceName }} +spec: + ports: + - port: 2181 + protocol: TCP + selector: + app: {{ .Values.zookeeperName }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.kafkaName }} + labels: + app: {{ .Values.kafkaName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.kafkaName }} + template: + metadata: + labels: + app: {{ .Values.kafkaName }} + spec: + securityContext: + runAsUser: 0 + runAsGroup: 0 + fsGroup: 0 + containers: + - name: {{ .Values.kafkaName }} + image: "{{ .Values.kafkaImageName }}:{{ .Values.kafkaImageVersion }}" + imagePullPolicy: Always + resources: + limits: + memory: "1G" + cpu: "1.5" + requests: + memory: "768M" + cpu: "1.0" + env: + {{- include "helpers.list-envKafkaApp-variables" . | indent 10 }} + ports: + - containerPort: 9092 +# volumeMounts: +# - name: hostvol +# mountPath: /bitnami/kafka +# volumes: +# - name: hostvol +# persistentVolumeClaim: +# claimName: {{ .Values.volumeClaimName }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.kafkaServiceName }} + labels: + run: {{ .Values.kafkaServiceName }} +spec: + type: NodePort + ports: + - port: 9092 + nodePort: 9092 + protocol: TCP + selector: + app: {{ .Values.kafkaName }} \ No newline at end of file diff --git a/minikube/kafka/values.yaml b/minikube/kafka/values.yaml new file mode 100644 index 00000000..01529648 --- /dev/null +++ b/minikube/kafka/values.yaml @@ -0,0 +1,33 @@ +kafkaName: kafkaapp +zookeeperName: zookeeperserver +kafkaImageName: bitnami/kafka +kafkaImageVersion: latest +zookeeperImageName: bitnami/zookeeper +zookeeperImageVersion: latest +kafkaServiceName: kafkaservice +zookeeperServiceName: zookeeperservice +volumeClaimName: mongo-pv-claim +persistentVolumeName: mongo-pv-volume + +secret: + name: app-env-secret + nameKafka: kafka-env-secret + nameZookeeper: zookeeper-env-secret + +envZookeeper: + normal: + ALLOW_ANONYMOUS_LOGIN: yes + secret: + ZOOKEEPER_TICK_TIME: "2000" + +envKafka: + normal: + KAFKA_BROKER_ID: "1" + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_CFG_LISTENERS: PLAINTEXT://:9092 + KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://:9092 + KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: false + ALLOW_PLAINTEXT_LISTENER: yes + KAFKA_ENABLE_KRAFT: false + secret: + KAFKA_ZOOKEEPER_CONNECT: "zookeeperservice:2181" \ No newline at end of file diff --git a/minikube/minikubeSetup.sh b/minikube/minikubeSetup.sh new file mode 100644 index 00000000..e3c61032 --- /dev/null +++ b/minikube/minikubeSetup.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# execute helmCommand.sh + +kubectl get services +minikube ip +http://:/ + +minikube config set memory 16384 +minikube config set cpu 2 +#minikube config set cpu 4 +minikube config set driver docker +minikube addons list +minikube addons enable metrics-server +kubectl edit deployment -n kube-system metrics-server + +kubectl logs --previous +kubectl exec --stdin --tty -- /bin/bash +kubectl expose pod --port=27017 --type="NodePort" +mongorestore -v --gzip mongodb://: + +minikube start --extra-config=apiserver.service-node-port-range=1024-65535 + +minikube pause +minikube unpause diff --git a/mvnw b/mvnw index 5bf251c0..a16b5431 100755 --- a/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Maven Start Up Batch script # # Required ENV vars: # ------------------ @@ -108,13 +108,12 @@ if $cygwin ; then CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi -# For Migwn, ensure paths are in UNIX format before anything is touched +# For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? fi if [ -z "$JAVA_HOME" ]; then @@ -200,8 +199,89 @@ if [ -z "$BASE_DIR" ]; then exit 1; fi +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -echo $MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java @@ -216,6 +296,11 @@ if $cygwin; then MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ diff --git a/mvnw.cmd b/mvnw.cmd index 019bd74d..c8d43372 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -18,7 +18,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @@ -26,7 +26,7 @@ @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -35,7 +35,9 @@ @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME @@ -115,10 +117,47 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end diff --git a/pom.xml b/pom.xml index 3df1a074..7b7eabf3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,24 +1,32 @@ - + + 4.0.0 ch.xxx - trader + angularandspring 0.0.1-SNAPSHOT - jar - demo + pom + angularandspring Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent - 2.0.0.RELEASE + 3.5.0 UTF-8 UTF-8 - 1.8 + 24 angular2guy + + test @@ -27,148 +35,9 @@ http://www.apache.org/licenses/LICENSE-2.0.html - - - org.springframework.boot - spring-boot-starter-data-mongodb-reactive - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - org.springframework.boot - spring-boot-starter-jetty - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-test - test - - - io.projectreactor - reactor-test - test - - - org.tuckey - urlrewritefilter - 4.0.3 - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - com.spotify - dockerfile-maven-plugin - 1.3.6 - - ${docker.image.prefix}/${project.artifactId} - - - - maven-clean-plugin - - false - - - src/angular/trader/dist - false - - - src/angular/trader/node_modules - false - - - - - - exec-maven-plugin - org.codehaus.mojo - 1.4.0 - - - npm install - - exec - - generate-sources - - npm - - install - - src/angular/trader - - - - - - - angular-cli build de - - exec - - generate-resources - - npm - - run - build - - src/angular/trader - - - - - - - - - lifecycle-mapping - org.eclipse.m2e - 1.0.0 - - - - - - exec-maven-plugin - org.codehaus.mojo - [1.4.0,) - - exec - - - - - - - - - - - - - + + frontend + backend + \ No newline at end of file diff --git a/prometheus-local.yml b/prometheus-local.yml new file mode 100644 index 00000000..b1b53f84 --- /dev/null +++ b/prometheus-local.yml @@ -0,0 +1,15 @@ +global: + scrape_interval: 15s + +scrape_configs: +- job_name: 'prometheus' + scrape_interval: 5s + + static_configs: + - targets: ['localhost:9090'] + +- job_name: 'spring-actuator' + metrics_path: '/actuator/prometheus' + scrape_interval: 5s + static_configs: + - targets: ['localhost:8080'] \ No newline at end of file diff --git a/runDocker.sh b/runDocker.sh index 0060cc27..2c3a41c2 100755 --- a/runDocker.sh +++ b/runDocker.sh @@ -1,2 +1,4 @@ #!/bin/sh -docker run -p 8080:8080 --network="host" angular2guy/trader:latest \ No newline at end of file +docker pull mongo:4.4 +docker run --name local-mongo -p 27017:27017 --cpus=2.0 --volume tmp/mongodb --memory=3g mongo:4.4 --wiredTigerCacheSizeGB 2.0 +docker run -p 8080:8080 --memory="1g" --network="host" angular2guy/angularandspring:latest \ No newline at end of file diff --git a/runGrafana.sh b/runGrafana.sh new file mode 100644 index 00000000..c4290568 --- /dev/null +++ b/runGrafana.sh @@ -0,0 +1,7 @@ +#!/bin/sh +#docker pull grafana/grafana +MYID=`id -u` +#data will be stored in ~/tmp/data +#mkdir ~/tmp/data +MYDATA=echo ~/tmp/data +docker run --user "$MYID" --volume "$MYID:$MYDATA" --network "host" grafana/grafana \ No newline at end of file diff --git a/runKafka.sh b/runKafka.sh new file mode 100644 index 00000000..780139c8 --- /dev/null +++ b/runKafka.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# network config for KRaft +docker network create app-tier --driver bridge +# Kafka with KRaft +docker run -d \ + -p 9092:9092 \ + --name kafka-server \ + --hostname kafka-server \ + --network app-tier \ + -e KAFKA_CFG_NODE_ID=0 \ + -e KAFKA_CFG_PROCESS_ROLES=controller,broker \ + -e KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \ + -e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \ + -e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka-server:9093 \ + -e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \ + bitnami/kafka:latest +# Start Kafka with KRaft +docker start kafka-server \ No newline at end of file diff --git a/runMongoDBDocker.sh b/runMongoDBDocker.sh new file mode 100644 index 00000000..6600c0ac --- /dev/null +++ b/runMongoDBDocker.sh @@ -0,0 +1,7 @@ +#!/bin/sh +docker pull mongo:6.0 +docker run --name local-mongo -p 27017:27017 --cpus=2.0 --memory=3g mongo:4.4 --wiredTigerCacheSizeGB 2.0 +#docker run --name local-mongo -p 27017:27017 --cpus=1.0 --memory=2g -v :/data/db mongo:6.0 --wiredTigerCacheSizeGB 1.0 +#docker start local-mongo +#docker stop local-mongo +#docker exec -it local-mongo bash \ No newline at end of file diff --git a/runPrometheus.sh b/runPrometheus.sh new file mode 100644 index 00000000..80695c63 --- /dev/null +++ b/runPrometheus.sh @@ -0,0 +1,4 @@ +#!/bin/sh +#docker pull prom/prometheus +LOCALDIR=pwd +docker run --network "host" -v "$LOCALDIR/prometheus-local.yml:/etc/prometheus/prometheus.yml" prom/prometheus \ No newline at end of file diff --git a/runStructurizr.sh b/runStructurizr.sh new file mode 100644 index 00000000..ee46ef7f --- /dev/null +++ b/runStructurizr.sh @@ -0,0 +1,2 @@ +docker pull structurizr/lite +docker run -it --rm -p 8080:8080 -v ~/git/AngularAndSpring/structurizr:/usr/local/structurizr structurizr/lite \ No newline at end of file diff --git a/src/angular/trader/.angular-cli.json b/src/angular/trader/.angular-cli.json deleted file mode 100644 index 801acd87..00000000 --- a/src/angular/trader/.angular-cli.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "project": { - "name": "trader" - }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "assets": [ - "assets", - "favicon.ico" - ], - "index": "index.html", - "main": "main.ts", - "polyfills": "polyfills.ts", - "test": "test.ts", - "tsconfig": "tsconfig.app.json", - "testTsconfig": "tsconfig.spec.json", - "prefix": "app", - "styles": [ - "styles.scss" - ], - "scripts": [], - "environmentSource": "environments/environment.ts", - "environments": { - "dev": "environments/environment.ts", - "prod": "environments/environment.prod.ts" - } - } - ], - "e2e": { - "protractor": { - "config": "./protractor.conf.js" - } - }, - "lint": [ - { - "project": "src/tsconfig.app.json", - "exclude": "**/node_modules/**" - }, - { - "project": "src/tsconfig.spec.json", - "exclude": "**/node_modules/**" - }, - { - "project": "e2e/tsconfig.e2e.json", - "exclude": "**/node_modules/**" - } - ], - "test": { - "karma": { - "config": "./karma.conf.js" - } - }, - "defaults": { - "styleExt": "scss", - "component": {} - } -} diff --git a/src/angular/trader/i18n/messages.de.xlf b/src/angular/trader/i18n/messages.de.xlf deleted file mode 100644 index e43b77ed..00000000 --- a/src/angular/trader/i18n/messages.de.xlf +++ /dev/null @@ -1,603 +0,0 @@ - - - - - - Username - Username - - app/login/login.component.ts - 9 - - - app/login/login.component.ts - 34 - - - - Password - Password - - app/login/login.component.ts - 15 - - - app/login/login.component.ts - 40 - - - app/login/login.component.ts - 44 - - - - Login Failed - Login Failed - - app/login/login.component.ts - 18 - - - - Ok - Ok - - app/login/login.component.ts - 22 - - - app/login/login.component.ts - 60 - - - - Cancel - Cancel - - app/login/login.component.ts - 23 - - - app/login/login.component.ts - 61 - - - - Login - Login - - app/login/login.component.ts - 3 - - - app/quoteoverview/quoteoverview.component.ts - 6 - - - - Passwords do not match - Passwords do not match - - app/login/login.component.ts - 47 - - - - Email - Email - - app/login/login.component.ts - 53 - - - - Signin Failed - Signin Failed - - app/login/login.component.ts - 56 - - - - Signin - Signin - - app/login/login.component.ts - 28 - - - - Curreny Table - Währungstabelle - - app/quoteoverview/quoteoverview.component.ts - 3 - - - - Orderbooks - Orderbuch - - app/quoteoverview/quoteoverview.component.ts - 5 - - - app/orderbooks/orderbooks.component.ts - 3 - - - - Logout - Logout - - app/quoteoverview/quoteoverview.component.ts - 7 - - - - Exchange - Börse - - app/quoteoverview/quoteoverview.component.ts - 12 - - - - Currency Pair - Währungspaar - - app/quoteoverview/quoteoverview.component.ts - 16 - - - - Last - Letzte - - app/quoteoverview/quoteoverview.component.ts - 19 - - - - Volume - Volumen - - app/quoteoverview/quoteoverview.component.ts - 32 - - - - Bitstamp - Bitstamp - - app/bsdetail/bsdetail.component.ts - 2 - - - app/orderbooks/orderbooks.component.ts - 9 - - - - Last - Last - - app/bsdetail/bsdetail.component.ts - 7 - - - app/ibdetail/ibdetail.component.ts - 7 - - - app/bfdetail/bfdetail.component.ts - 7 - - - - High - High - - app/quoteoverview/quoteoverview.component.ts - 24 - - - app/bsdetail/bsdetail.component.ts - 9 - - - app/ibdetail/ibdetail.component.ts - 9 - - - app/bfdetail/bfdetail.component.ts - 9 - - - - Low - Low - - app/quoteoverview/quoteoverview.component.ts - 25 - - - app/bsdetail/bsdetail.component.ts - 11 - - - app/ibdetail/ibdetail.component.ts - 11 - - - app/bfdetail/bfdetail.component.ts - 11 - - - - Bid - Bid - - app/bsdetail/bsdetail.component.ts - 15 - - - app/ibdetail/ibdetail.component.ts - 15 - - - app/bfdetail/bfdetail.component.ts - 15 - - - - Ask - Ask - - app/bsdetail/bsdetail.component.ts - 17 - - - app/ibdetail/ibdetail.component.ts - 17 - - - app/bfdetail/bfdetail.component.ts - 17 - - - - Open - Open - - app/bsdetail/bsdetail.component.ts - 19 - - - app/ibdetail/ibdetail.component.ts - 19 - - - - Vwap - Vwap - - app/bsdetail/bsdetail.component.ts - 23 - - - app/ibdetail/ibdetail.component.ts - 23 - - - - Pair - Pair - - app/bsdetail/bsdetail.component.ts - 25 - - - app/ibdetail/ibdetail.component.ts - 25 - - - app/cbdetail/cbdetail.component.ts - 7 - - - app/cbdetail/cbdetail.component.ts - 21 - - - app/cbdetail/cbdetail.component.ts - 35 - - - app/bfdetail/bfdetail.component.ts - 23 - - - - Timestamp - Timestamp - - app/bsdetail/bsdetail.component.ts - 27 - - - app/ibdetail/ibdetail.component.ts - 27 - - - app/cbdetail/cbdetail.component.ts - 17 - - - app/cbdetail/cbdetail.component.ts - 31 - - - app/cbdetail/cbdetail.component.ts - 45 - - - app/bfdetail/bfdetail.component.ts - 25 - - - - Volume - Volume - - app/bsdetail/bsdetail.component.ts - 31 - - - app/ibdetail/ibdetail.component.ts - 31 - - - app/bfdetail/bfdetail.component.ts - 27 - - - - back - back - - app/bsdetail/bsdetail.component.ts - 37 - - - app/ibdetail/ibdetail.component.ts - 37 - - - app/cbdetail/cbdetail.component.ts - 51 - - - app/bfdetail/bfdetail.component.ts - 33 - - - - {VAR_PLURAL, plural, =1 {today} other { days} } - {VAR_PLURAL, plural, =1 {heute} other { Tage} } - - app/bsdetail/bsdetail.component.ts - 39 - - - app/ibdetail/ibdetail.component.ts - 39 - - - app/cbdetail/cbdetail.component.ts - 53 - - - app/bfdetail/bfdetail.component.ts - 35 - - - - ItBit - ItBit - - app/ibdetail/ibdetail.component.ts - 2 - - - - Coinbase - Coinbase - - app/cbdetail/cbdetail.component.ts - 2 - - - - Last Usd - Last Usd - - app/cbdetail/cbdetail.component.ts - 9 - - - app/cbdetail/cbdetail.component.ts - 23 - - - app/cbdetail/cbdetail.component.ts - 37 - - - - Last Eur - Last Eur - - app/cbdetail/cbdetail.component.ts - 11 - - - app/cbdetail/cbdetail.component.ts - 25 - - - app/cbdetail/cbdetail.component.ts - 39 - - - - Last Yen - Last Yen - - app/cbdetail/cbdetail.component.ts - 13 - - - app/cbdetail/cbdetail.component.ts - 27 - - - app/cbdetail/cbdetail.component.ts - 41 - - - - Last Pound - Last Pound - - app/cbdetail/cbdetail.component.ts - 15 - - - app/cbdetail/cbdetail.component.ts - 29 - - - app/cbdetail/cbdetail.component.ts - 43 - - - - Bitfinex - Bitfinex - - app/bfdetail/bfdetail.component.ts - 2 - - - app/orderbooks/orderbooks.component.ts - 15 - - - - Mid - Mid - - app/bfdetail/bfdetail.component.ts - 19 - - - - Itbit - Itbit - - app/orderbooks/orderbooks.component.ts - 12 - - - - Buy - Buy - - app/orderbooks/orderbooks.component.ts - 20 - - - - Sell - Sell - - app/orderbooks/orderbooks.component.ts - 21 - - - - Search - Search - - app/orderbooks/orderbooks.component.ts - 44 - - - - Back - Back - - app/orderbooks/orderbooks.component.ts - 45 - - - - Bitstamp Orders - Bitstamp Orders - - app/orderbooks/orderbooks.component.ts - 52 - - - - Price - Price - - app/orderbooks/orderbooks.component.ts - 53 - - - app/orderbooks/orderbooks.component.ts - 62 - - - app/orderbooks/orderbooks.component.ts - 71 - - - - Amount - Amount - - app/orderbooks/orderbooks.component.ts - 53 - - - app/orderbooks/orderbooks.component.ts - 62 - - - app/orderbooks/orderbooks.component.ts - 71 - - - - Itbit Orders - Itbit Orders - - app/orderbooks/orderbooks.component.ts - 61 - - - - Bitfinex Orders - Bitfinex Orders - - app/orderbooks/orderbooks.component.ts - 70 - - - - - diff --git a/src/angular/trader/i18n/messages.xlf b/src/angular/trader/i18n/messages.xlf deleted file mode 100644 index 8828e67a..00000000 --- a/src/angular/trader/i18n/messages.xlf +++ /dev/null @@ -1,603 +0,0 @@ - - - - - - Username - Username - - app/login/login.component.ts - 9 - - - app/login/login.component.ts - 34 - - - - Password - Password - - app/login/login.component.ts - 15 - - - app/login/login.component.ts - 40 - - - app/login/login.component.ts - 44 - - - - Login Failed - Login Failed - - app/login/login.component.ts - 18 - - - - Ok - Ok - - app/login/login.component.ts - 22 - - - app/login/login.component.ts - 60 - - - - Cancel - Cancel - - app/login/login.component.ts - 23 - - - app/login/login.component.ts - 61 - - - - Login - Login - - app/login/login.component.ts - 3 - - - app/quoteoverview/quoteoverview.component.ts - 6 - - - - Passwords do not match - Passwords do not match - - app/login/login.component.ts - 47 - - - - Email - Email - - app/login/login.component.ts - 53 - - - - Signin Failed - Signin Failed - - app/login/login.component.ts - 56 - - - - Signin - Signin - - app/login/login.component.ts - 28 - - - - Curreny Table - Curreny Table - - app/quoteoverview/quoteoverview.component.ts - 3 - - - - Orderbooks - Orderbooks - - app/quoteoverview/quoteoverview.component.ts - 5 - - - app/orderbooks/orderbooks.component.ts - 3 - - - - Logout - Logout - - app/quoteoverview/quoteoverview.component.ts - 7 - - - - Exchange - Exchange - - app/quoteoverview/quoteoverview.component.ts - 12 - - - - Currency Pair - Currency Pair - - app/quoteoverview/quoteoverview.component.ts - 16 - - - - Last - Last - - app/quoteoverview/quoteoverview.component.ts - 19 - - - - Volume - Volume - - app/quoteoverview/quoteoverview.component.ts - 32 - - - - Bitstamp - Bitstamp - - app/bsdetail/bsdetail.component.ts - 2 - - - app/orderbooks/orderbooks.component.ts - 9 - - - - Last - Last - - app/bsdetail/bsdetail.component.ts - 7 - - - app/ibdetail/ibdetail.component.ts - 7 - - - app/bfdetail/bfdetail.component.ts - 7 - - - - High - High - - app/quoteoverview/quoteoverview.component.ts - 24 - - - app/bsdetail/bsdetail.component.ts - 9 - - - app/ibdetail/ibdetail.component.ts - 9 - - - app/bfdetail/bfdetail.component.ts - 9 - - - - Low - Low - - app/quoteoverview/quoteoverview.component.ts - 25 - - - app/bsdetail/bsdetail.component.ts - 11 - - - app/ibdetail/ibdetail.component.ts - 11 - - - app/bfdetail/bfdetail.component.ts - 11 - - - - Bid - Bid - - app/bsdetail/bsdetail.component.ts - 15 - - - app/ibdetail/ibdetail.component.ts - 15 - - - app/bfdetail/bfdetail.component.ts - 15 - - - - Ask - Ask - - app/bsdetail/bsdetail.component.ts - 17 - - - app/ibdetail/ibdetail.component.ts - 17 - - - app/bfdetail/bfdetail.component.ts - 17 - - - - Open - Open - - app/bsdetail/bsdetail.component.ts - 19 - - - app/ibdetail/ibdetail.component.ts - 19 - - - - Vwap - Vwap - - app/bsdetail/bsdetail.component.ts - 23 - - - app/ibdetail/ibdetail.component.ts - 23 - - - - Pair - Pair - - app/bsdetail/bsdetail.component.ts - 25 - - - app/ibdetail/ibdetail.component.ts - 25 - - - app/cbdetail/cbdetail.component.ts - 7 - - - app/cbdetail/cbdetail.component.ts - 21 - - - app/cbdetail/cbdetail.component.ts - 35 - - - app/bfdetail/bfdetail.component.ts - 23 - - - - Timestamp - Timestamp - - app/bsdetail/bsdetail.component.ts - 27 - - - app/ibdetail/ibdetail.component.ts - 27 - - - app/cbdetail/cbdetail.component.ts - 17 - - - app/cbdetail/cbdetail.component.ts - 31 - - - app/cbdetail/cbdetail.component.ts - 45 - - - app/bfdetail/bfdetail.component.ts - 25 - - - - Volume - Volume - - app/bsdetail/bsdetail.component.ts - 31 - - - app/ibdetail/ibdetail.component.ts - 31 - - - app/bfdetail/bfdetail.component.ts - 27 - - - - back - back - - app/bsdetail/bsdetail.component.ts - 37 - - - app/ibdetail/ibdetail.component.ts - 37 - - - app/cbdetail/cbdetail.component.ts - 51 - - - app/bfdetail/bfdetail.component.ts - 33 - - - - {VAR_PLURAL, plural, =1 {today} other { days} } - {VAR_PLURAL, plural, =1 {today} other { days} } - - app/bsdetail/bsdetail.component.ts - 39 - - - app/ibdetail/ibdetail.component.ts - 39 - - - app/cbdetail/cbdetail.component.ts - 53 - - - app/bfdetail/bfdetail.component.ts - 35 - - - - ItBit - ItBit - - app/ibdetail/ibdetail.component.ts - 2 - - - - Coinbase - Coinbase - - app/cbdetail/cbdetail.component.ts - 2 - - - - Last Usd - Last Usd - - app/cbdetail/cbdetail.component.ts - 9 - - - app/cbdetail/cbdetail.component.ts - 23 - - - app/cbdetail/cbdetail.component.ts - 37 - - - - Last Eur - Last Eur - - app/cbdetail/cbdetail.component.ts - 11 - - - app/cbdetail/cbdetail.component.ts - 25 - - - app/cbdetail/cbdetail.component.ts - 39 - - - - Last Yen - Last Yen - - app/cbdetail/cbdetail.component.ts - 13 - - - app/cbdetail/cbdetail.component.ts - 27 - - - app/cbdetail/cbdetail.component.ts - 41 - - - - Last Pound - Last Pound - - app/cbdetail/cbdetail.component.ts - 15 - - - app/cbdetail/cbdetail.component.ts - 29 - - - app/cbdetail/cbdetail.component.ts - 43 - - - - Bitfinex - Bitfinex - - app/bfdetail/bfdetail.component.ts - 2 - - - app/orderbooks/orderbooks.component.ts - 15 - - - - Mid - Mid - - app/bfdetail/bfdetail.component.ts - 19 - - - - Itbit - Itbit - - app/orderbooks/orderbooks.component.ts - 12 - - - - Buy - Buy - - app/orderbooks/orderbooks.component.ts - 20 - - - - Sell - Sell - - app/orderbooks/orderbooks.component.ts - 21 - - - - Search - Search - - app/orderbooks/orderbooks.component.ts - 44 - - - - Back - Back - - app/orderbooks/orderbooks.component.ts - 45 - - - - Bitstamp Orders - Bitstamp Orders - - app/orderbooks/orderbooks.component.ts - 52 - - - - Price - Price - - app/orderbooks/orderbooks.component.ts - 53 - - - app/orderbooks/orderbooks.component.ts - 62 - - - app/orderbooks/orderbooks.component.ts - 71 - - - - Amount - Amount - - app/orderbooks/orderbooks.component.ts - 53 - - - app/orderbooks/orderbooks.component.ts - 62 - - - app/orderbooks/orderbooks.component.ts - 71 - - - - Itbit Orders - Itbit Orders - - app/orderbooks/orderbooks.component.ts - 61 - - - - Bitfinex Orders - Bitfinex Orders - - app/orderbooks/orderbooks.component.ts - 70 - - - - - diff --git a/src/angular/trader/karma.conf.js b/src/angular/trader/karma.conf.js deleted file mode 100644 index 112340a2..00000000 --- a/src/angular/trader/karma.conf.js +++ /dev/null @@ -1,34 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular/cli'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular/cli/plugins/karma') - ], - client:{ - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - reports: [ 'html', 'lcovonly' ], - fixWebpackSourcePaths: true - }, - angularCli: { - environment: 'dev' - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false - }); -}; - diff --git a/src/angular/trader/package.json b/src/angular/trader/package.json deleted file mode 100644 index e6302102..00000000 --- a/src/angular/trader/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "trader", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "ng": "ng", - "start": "ng serve --proxy-config proxy.conf.js", - "build": "ng build --prod --aot --bh /de --output-path dist/de --locale de --deploy-url /de --i18nFile=./i18n/messages.de.xlf && ng build --prod --aot --bh /en --deploy-url /en --output-path dist/en --locale en --i18nFile=./i18n/messages.xlf", - "test": "ng test", - "lint": "ng lint", - "e2e": "ng e2e", - "prebuild": "rimraf ./dist && mkdirp ./dist", - "postbuild": "npm run deploy", - "predeploy": "rimraf ../../main/resources/static/de && mkdirp ../../main/resources/static/de && rimraf ../../main/resources/static/en && mkdirp ../../main/resources/static/en", - "deploy": "copyfiles -f dist/en/** ../../main/resources/static/en/ && copyfiles -f dist/de/** ../../main/resources/static/de/" - }, - "private": true, - "dependencies": { - "@angular/animations": "^5.2.0", - "@angular/common": "^5.2.0", - "@angular/compiler": "^5.2.0", - "@angular/core": "^5.2.0", - "@angular/forms": "^5.2.0", - "@angular/http": "^5.2.0", - "@angular/platform-browser": "^5.2.0", - "@angular/platform-browser-dynamic": "^5.2.0", - "@angular/router": "^5.2.0", - "core-js": "^2.4.1", - "rxjs": "^5.5.6", - "zone.js": "^0.8.19", - "web-animations-js": "^2.3.1", - - "chart.js": "~2.7.0", - "ng2-charts": "1.6.0", - "@angular/material": "^5.2.0", - "@angular/cdk": "^5.2.0", - "material-design-icons": "3.0.1" - }, - "devDependencies": { - "@angular/cli": "1.7.0", - "@angular/compiler-cli": "^5.2.0", - "@angular/language-service": "^5.2.0", - "@types/jasmine": "~2.8.3", - "@types/jasminewd2": "~2.0.2", - "@types/node": "~6.0.60", - "codelyzer": "^4.0.1", - "jasmine-core": "~2.8.0", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~2.0.0", - "karma-chrome-launcher": "~2.2.0", - "karma-coverage-istanbul-reporter": "^1.2.1", - "karma-jasmine": "~1.1.0", - "karma-jasmine-html-reporter": "^0.2.2", - "protractor": "~5.1.2", - "ts-node": "~4.1.0", - "tslint": "~5.9.1", - "typescript": "~2.5.3", - - "rimraf": "^2.6.1", - "mkdirp": "^0.5.1", - "copyfiles": "^1.2.0" - } -} diff --git a/src/angular/trader/protractor.conf.js b/src/angular/trader/protractor.conf.js deleted file mode 100644 index 7ee3b5ee..00000000 --- a/src/angular/trader/protractor.conf.js +++ /dev/null @@ -1,28 +0,0 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './e2e/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: 'e2e/tsconfig.e2e.json' - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; diff --git a/src/angular/trader/src/app/app-routing.module.ts b/src/angular/trader/src/app/app-routing.module.ts deleted file mode 100644 index 0cfb8393..00000000 --- a/src/angular/trader/src/app/app-routing.module.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { QuoteoverviewComponent } from './quoteoverview/quoteoverview.component'; -import { BsdetailComponent } from './bsdetail/bsdetail.component'; -import { IbdetailComponent } from './ibdetail/ibdetail.component'; -import { CbdetailComponent } from './cbdetail/cbdetail.component'; -import { BfdetailComponent } from './bfdetail/bfdetail.component'; -import { OrderbooksComponent } from './orderbooks/orderbooks.component'; -import { AuthGuardService } from './services/auth-guard.service'; - -const routes: Routes = [ - {path: 'overview', component: QuoteoverviewComponent}, - {path: 'bsdetail/:currpair', component: BsdetailComponent}, - {path: 'ibdetail/:currpair', component: IbdetailComponent}, - {path: 'cbdetail/:currpair', component: CbdetailComponent}, - {path: 'bfdetail/:currpair', component: BfdetailComponent}, - {path: 'orderbooks', component: OrderbooksComponent, canActivate: [AuthGuardService]}, - {path: '**', component: QuoteoverviewComponent} -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule { } diff --git a/src/angular/trader/src/app/app.component.html b/src/angular/trader/src/app/app.component.html deleted file mode 100644 index cb77bc7d..00000000 --- a/src/angular/trader/src/app/app.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/angular/trader/src/app/app.component.spec.ts b/src/angular/trader/src/app/app.component.spec.ts deleted file mode 100644 index ce3abcb4..00000000 --- a/src/angular/trader/src/app/app.component.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; -describe('AppComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - })); - it('should create the app', async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - })); - it(`should have as title 'app'`, async(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('app'); - })); - it('should render title in a h1 tag', async(() => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); - })); -}); diff --git a/src/angular/trader/src/app/app.module.ts b/src/angular/trader/src/app/app.module.ts deleted file mode 100644 index 38ab8e0c..00000000 --- a/src/angular/trader/src/app/app.module.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; -import { FormsModule,ReactiveFormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import { HttpClientModule } from '@angular/common/http'; - -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { QuoteoverviewComponent } from './quoteoverview/quoteoverview.component'; -import { BitstampService } from './services/bitstamp.service'; -import { CoinbaseService } from './services/coinbase.service'; -import { ItbitService } from './services/itbit.service'; -import { BitfinexService} from './services/bitfinex.service'; -import { BsdetailComponent } from './bsdetail/bsdetail.component'; -import { IbdetailComponent } from './ibdetail/ibdetail.component'; -import { MaterialModule } from './material.module'; -import { ChartsModule } from 'ng2-charts'; -import { CbdetailComponent } from './cbdetail/cbdetail.component'; -import { BfdetailComponent } from './bfdetail/bfdetail.component'; -import { LoginComponent } from './login/login.component'; -import { MyuserService } from './services/myuser.service'; -import { OrderbooksComponent } from './orderbooks/orderbooks.component'; -import { AuthGuardService } from './services/auth-guard.service'; - - -@NgModule({ - declarations: [ - AppComponent, - QuoteoverviewComponent, - BsdetailComponent, - IbdetailComponent, - CbdetailComponent, - BfdetailComponent, - LoginComponent, - OrderbooksComponent - - ], - entryComponents: [ - LoginComponent - ], - imports: [ - BrowserModule, - FormsModule, - ReactiveFormsModule, - HttpModule, - HttpClientModule, - BrowserAnimationsModule, - MaterialModule, - ChartsModule, - AppRoutingModule - ], - providers: [BitstampService, - CoinbaseService, - ItbitService, - BitfinexService, - MyuserService, - AuthGuardService], - bootstrap: [AppComponent] -}) -export class AppModule { } diff --git a/src/angular/trader/src/app/bfdetail/bfdetail.component.html b/src/angular/trader/src/app/bfdetail/bfdetail.component.html deleted file mode 100644 index 5b2b1e98..00000000 --- a/src/angular/trader/src/app/bfdetail/bfdetail.component.html +++ /dev/null @@ -1,50 +0,0 @@ -
- Bitfinex {{currPair}} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Last:{{currQuote?.last_price | number:'1.2'}}High:{{currQuote?.high | number:'1.2'}}Low:{{currQuote?.low | number:'1.2'}}
Bid:{{currQuote?.bid | number:'1.2'}}Ask:{{currQuote?.ask | number:'1.2'}}Mid:{{currQuote?.mid | number:'1.2'}}
Pair:{{currPair}}Timestamp:{{currQuote?.createdAt | date:'HH:mm:ss' }}Volume:{{currQuote?.volume | number:'1.2'}}
- -
- -
-
{{todayQuotes?.length }} -
-
diff --git a/src/angular/trader/src/app/bfdetail/bfdetail.component.scss b/src/angular/trader/src/app/bfdetail/bfdetail.component.scss deleted file mode 100644 index 5a688878..00000000 --- a/src/angular/trader/src/app/bfdetail/bfdetail.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -td { - width: 150px; -} -.radioGroup { - margin-left: 100px; -} -.radioButton { - margin-left: 10px; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/bfdetail/bfdetail.component.ts b/src/angular/trader/src/app/bfdetail/bfdetail.component.ts deleted file mode 100644 index be171c0c..00000000 --- a/src/angular/trader/src/app/bfdetail/bfdetail.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Component, OnInit } from '@angular/core'; -import {ActivatedRoute, Router, ParamMap } from '@angular/router'; -import { trigger, state, animate, transition, style } from '@angular/animations'; -import 'rxjs/add/operator/switchMap'; -import { BitfinexService } from '../services/bitfinex.service'; -import { QuoteBf } from '../common/quoteBf'; -import { CommonUtils } from '../common/commonUtils'; - -@Component({ - selector: 'app-bfdetail', - templateUrl: './bfdetail.component.html', - styleUrls: ['./bfdetail.component.scss'], - animations: [ - trigger('showChart', [ - state('true' , style({ opacity: 1 })), - state('false', style({ opacity: 0 })), - transition('1 => 0', animate('300ms')), - transition('0 => 1', animate('300ms')) - ]) - ] -}) -export class BfdetailComponent implements OnInit { - - currQuote: QuoteBf; - todayQuotes: QuoteBf[] = []; - chartdata: number[] = []; - chartlabels: string[] = []; - chartType = "line"; - utils = new CommonUtils(); - currPair = ""; - timeframe = this.utils.timeframes[0]; - - constructor(private route: ActivatedRoute, private router: Router, private serviceBf: BitfinexService) { } - - ngOnInit() { - this.route.paramMap - .switchMap((params: ParamMap) => this.serviceBf.getCurrentQuote(params.get('currpair'))) - .subscribe(quote => { - this.currQuote = quote; - this.currPair = this.utils.getCurrpairName(this.currQuote.pair);}); - this.route.paramMap - .switchMap((params: ParamMap) => this.serviceBf.getTodayQuotes(params.get('currpair'))) - .subscribe(quotes => { - this.todayQuotes = quotes; - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - this.chartdata = this.todayQuotes.map(quote => quote.last_price); - }); - } - - changeTf() { - this.chartdata = []; - this.chartlabels = []; - this.route.paramMap - .switchMap((params: ParamMap) => { - if(this.timeframe === this.utils.timeframes[1]) return this.serviceBf.get7DayQuotes(params.get('currpair')); - if(this.timeframe === this.utils.timeframes[2]) return this.serviceBf.get30DayQuotes(params.get('currpair')); - if(this.timeframe === this.utils.timeframes[3]) return this.serviceBf.get90DayQuotes(params.get('currpair')) - else return this.serviceBf.getTodayQuotes(params.get('currpair')); - }) - .subscribe(quotes => { - this.todayQuotes = quotes; - if(this.timeframe === this.utils.timeframes[2] || this.timeframe === this.utils.timeframes[3]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getUTCDate().toString()) - else if(this.timeframe === this.utils.timeframes[1]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getDay().toString()) - else - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - this.chartdata = this.todayQuotes.map(quote => quote.last_price); - }); - } -} diff --git a/src/angular/trader/src/app/bsdetail/bsdetail.component.html b/src/angular/trader/src/app/bsdetail/bsdetail.component.html deleted file mode 100644 index d92f352a..00000000 --- a/src/angular/trader/src/app/bsdetail/bsdetail.component.html +++ /dev/null @@ -1,53 +0,0 @@ -
- Bitstamp {{currPair}} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Last:{{currQuote?.last | number:'1.2'}}High:{{currQuote?.high | number:'1.2'}}Low:{{currQuote?.low | number:'1.2'}}
Bid:{{currQuote?.bid | number:'1.2'}}Ask:{{currQuote?.ask | number:'1.2'}}Open:{{currQuote?.open | number:'1.2'}}
Vwap:{{currQuote?.vwap | number:'1.2'}}Pair:{{currPair}}Timestamp:{{currQuote?.createdAt | date:'HH:mm:ss' }}
Volume:{{currQuote?.volume | number:'1.2'}}
- -
- -
-
{{todayQuotes?.length }} -
-
\ No newline at end of file diff --git a/src/angular/trader/src/app/bsdetail/bsdetail.component.scss b/src/angular/trader/src/app/bsdetail/bsdetail.component.scss deleted file mode 100644 index 50b5db28..00000000 --- a/src/angular/trader/src/app/bsdetail/bsdetail.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -td { - width: 150px; -} -.radioGroup { - margin-left: 100px; -} -.radioButton { - margin-left: 10px; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/bsdetail/bsdetail.component.spec.ts b/src/angular/trader/src/app/bsdetail/bsdetail.component.spec.ts deleted file mode 100644 index ebf2994b..00000000 --- a/src/angular/trader/src/app/bsdetail/bsdetail.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { BsdetailComponent } from './bsdetail.component'; - -describe('BsdetailComponent', () => { - let component: BsdetailComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ BsdetailComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(BsdetailComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/angular/trader/src/app/bsdetail/bsdetail.component.ts b/src/angular/trader/src/app/bsdetail/bsdetail.component.ts deleted file mode 100644 index b46264ab..00000000 --- a/src/angular/trader/src/app/bsdetail/bsdetail.component.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Component, OnInit } from '@angular/core'; -import {ActivatedRoute, Router, ParamMap } from '@angular/router'; -import { trigger, state, animate, transition, style } from '@angular/animations'; -import 'rxjs/add/operator/switchMap'; -import { BitstampService } from '../services/bitstamp.service'; -import { QuoteBs } from '../common/quoteBs'; -import { CommonUtils } from '../common/commonUtils'; - -@Component({ - selector: 'app-bsdetail', - templateUrl: './bsdetail.component.html', - styleUrls: ['./bsdetail.component.scss'], - animations: [ - trigger('showChart', [ - state('true' , style({ opacity: 1 })), - state('false', style({ opacity: 0 })), - transition('1 => 0', animate('300ms')), - transition('0 => 1', animate('300ms')) - ]) - ] -}) -export class BsdetailComponent implements OnInit { - - currQuote: QuoteBs; - todayQuotes: QuoteBs[] = []; - chartdata: number[] = []; - chartlabels: string[] = []; - chartType = "line"; - utils = new CommonUtils(); - currPair = ""; - timeframe = this.utils.timeframes[0]; - - constructor(private route: ActivatedRoute, private router: Router, private serviceBs: BitstampService) { } - - ngOnInit() { - this.route.paramMap - .switchMap((params: ParamMap) => this.serviceBs.getCurrentQuote(params.get('currpair'))) - .subscribe(quote => { - this.currQuote = quote; - this.currPair = this.utils.getCurrpairName(this.currQuote.pair);}); - this.route.paramMap - .switchMap((params: ParamMap) => this.serviceBs.getTodayQuotes(params.get('currpair'))) - .subscribe(quotes => { - this.todayQuotes = quotes; - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - this.chartdata = this.todayQuotes.map(quote => quote.last); - }); - } - - changeTf() { - this.chartdata = []; - this.chartlabels = []; - this.route.paramMap - .switchMap((params: ParamMap) => { - if(this.timeframe === this.utils.timeframes[1]) return this.serviceBs.get7DayQuotes(params.get('currpair')); - if(this.timeframe === this.utils.timeframes[2]) return this.serviceBs.get30DayQuotes(params.get('currpair')); - if(this.timeframe === this.utils.timeframes[3]) return this.serviceBs.get90DayQuotes(params.get('currpair')) - else return this.serviceBs.getTodayQuotes(params.get('currpair')); - }) - .subscribe(quotes => { - this.todayQuotes = quotes; - if(this.timeframe === this.utils.timeframes[2] || this.timeframe === this.utils.timeframes[3]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getUTCDate().toString()) - else if(this.timeframe === this.utils.timeframes[1]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getDay().toString()) - else - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - this.chartdata = this.todayQuotes.map(quote => quote.last); - }); - } - -} diff --git a/src/angular/trader/src/app/cbdetail/cbdetail.component.html b/src/angular/trader/src/app/cbdetail/cbdetail.component.html deleted file mode 100644 index 5fd8708a..00000000 --- a/src/angular/trader/src/app/cbdetail/cbdetail.component.html +++ /dev/null @@ -1,67 +0,0 @@ -
- Coinbase {{myCurrPair}} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pair:{{myCurrPair}}Last Usd:{{currQuote?.usd | number:'1.2'}}Last Eur:{{currQuote?.eur | number:'1.2'}}Last Yen:{{currQuote?.jpy | number:'1.2'}}Last Pound:{{currQuote?.gbp | number:'1.2'}}Timestamp:{{currQuote?.createdAt | date:'HH:mm:ss' }}
Pair:{{myCurrPair}}Last Usd:{{currQuote?.usd / currQuote?.eth | number:'1.2'}}Last Eur:{{currQuote?.eur / currQuote?.eth | number:'1.2'}}Last Yen:{{currQuote?.jpy / currQuote?.eth | number:'1.2'}}Last Pound:{{currQuote?.gbp / currQuote?.eth | number:'1.2'}}Timestamp:{{currQuote?.createdAt | date:'HH:mm:ss' }}
Pair:{{myCurrPair}}Last Usd:{{currQuote?.usd / currQuote?.ltc | number:'1.2'}}Last Eur:{{currQuote?.eur / currQuote?.ltc | number:'1.2'}}Last Yen:{{currQuote?.jpy / currQuote?.ltc | number:'1.2'}}Last Pound:{{currQuote?.gbp / currQuote?.ltc | number:'1.2'}}Timestamp:{{currQuote?.createdAt | date:'HH:mm:ss' }}
- -
- -
-
{{todayQuotes?.length }} -
-
diff --git a/src/angular/trader/src/app/cbdetail/cbdetail.component.scss b/src/angular/trader/src/app/cbdetail/cbdetail.component.scss deleted file mode 100644 index 50b5db28..00000000 --- a/src/angular/trader/src/app/cbdetail/cbdetail.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -td { - width: 150px; -} -.radioGroup { - margin-left: 100px; -} -.radioButton { - margin-left: 10px; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/cbdetail/cbdetail.component.spec.ts b/src/angular/trader/src/app/cbdetail/cbdetail.component.spec.ts deleted file mode 100644 index 284f4370..00000000 --- a/src/angular/trader/src/app/cbdetail/cbdetail.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CbdetailComponent } from './cbdetail.component'; - -describe('CbdetailComponent', () => { - let component: CbdetailComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CbdetailComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CbdetailComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/angular/trader/src/app/cbdetail/cbdetail.component.ts b/src/angular/trader/src/app/cbdetail/cbdetail.component.ts deleted file mode 100644 index 54c6dabf..00000000 --- a/src/angular/trader/src/app/cbdetail/cbdetail.component.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router, ParamMap } from '@angular/router'; -import { trigger, state, animate, transition, style } from '@angular/animations'; -import 'rxjs/add/operator/switchMap'; -import { QuoteCb, QuoteCbSmall } from '../common/quoteCb'; -import { CoinbaseService } from '../services/coinbase.service'; -import { CommonUtils } from '../common/commonUtils'; - -@Component({ - selector: 'app-cbdetail', - templateUrl: './cbdetail.component.html', - styleUrls: ['./cbdetail.component.scss'], - animations: [ - trigger('showChart', [ - state('true' , style({ opacity: 1 })), - state('false', style({ opacity: 0 })), - transition('1 => 0', animate('300ms')), - transition('0 => 1', animate('300ms')) - ]) - ] -}) -export class CbdetailComponent implements OnInit { - - currQuote: QuoteCb; - todayQuotes: QuoteCbSmall[] = []; - chartdata: number[] = []; - chartlabels: string[] = []; - chartType = "line"; - currpair: string; - myCurrPair = ""; - utils = new CommonUtils(); - BTCUSD: string; - ETHUSD: string; - LTCUSD: string; - timeframe = this.utils.timeframes[0]; - - constructor(private route: ActivatedRoute, private router: Router, private serviceCb: CoinbaseService) { - this.BTCUSD = this.serviceCb.BTCUSD; - this.ETHUSD = this.serviceCb.ETHUSD; - this.LTCUSD = this.serviceCb.LTCUSD; - } - - ngOnInit() { - this.route.paramMap - .switchMap((params: ParamMap) => { - this.currpair = params.get('currpair'); - this.myCurrPair = this.utils.getCurrpairName(this.currpair); - return this.serviceCb.getCurrentQuote();}) - .subscribe(quote => this.currQuote = quote); - this.route.paramMap - .switchMap((params: ParamMap) => { - this.currpair = params.get('currpair'); - return this.serviceCb.getTodayQuotes();}) - .subscribe(quotes => { - this.todayQuotes = quotes; - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - if(this.currpair === this.serviceCb.BTCUSD) { - this.chartdata = this.todayQuotes.map(quote => quote.usd); - } else if(this.currpair === this.serviceCb.ETHUSD) { - this.chartdata = this.todayQuotes.map(quote => quote.usd/quote.eth) - } else if(this.currpair === this.serviceCb.LTCUSD) { - this.chartdata = this.todayQuotes.map(quote => quote.usd/quote.ltc) - } - }); - } - - changeTf() { - this.chartdata = []; - this.chartlabels = []; - this.route.paramMap - .switchMap((params: ParamMap) => { - this.currpair = params.get('currpair'); - if(this.timeframe === this.utils.timeframes[1]) return this.serviceCb.get7DayQuotes(); - if(this.timeframe === this.utils.timeframes[2]) return this.serviceCb.get30DayQuotes(); - if(this.timeframe === this.utils.timeframes[3]) return this.serviceCb.get90DayQuotes() - else return this.serviceCb.getTodayQuotes(); - }) - .subscribe(quotes => { - this.todayQuotes = quotes; - if(this.timeframe === this.utils.timeframes[2] || this.timeframe === this.utils.timeframes[3]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getUTCDate().toString()) - else if(this.timeframe === this.utils.timeframes[1]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getDay().toString()) - else - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - - if(this.currpair === this.serviceCb.BTCUSD) { - this.chartdata = this.todayQuotes.map(quote => quote.usd); - } else if(this.currpair === this.serviceCb.ETHUSD) { - this.chartdata = this.todayQuotes.map(quote => quote.usd/quote.eth) - } else if(this.currpair === this.serviceCb.LTCUSD) { - this.chartdata = this.todayQuotes.map(quote => quote.usd/quote.ltc) - }}); - } - -} diff --git a/src/angular/trader/src/app/common/commonUtils.ts b/src/angular/trader/src/app/common/commonUtils.ts deleted file mode 100644 index ae084b6b..00000000 --- a/src/angular/trader/src/app/common/commonUtils.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { BitstampService } from '../services/bitstamp.service'; -import { CoinbaseService } from '../services/coinbase.service'; -import { ItbitService } from '../services/itbit.service'; -import { BitfinexService } from '../services/bitfinex.service'; - -export class CommonUtils { - private currpairs = new Map(); - public timeframes = [1, 7, 30, 90]; - - constructor() { - let serviceBs = new BitstampService(null, null); - let serviceCb = new CoinbaseService(null, null); - let serviceIb = new ItbitService(null,null); - let serviceBf = new BitfinexService(null, null); - this.currpairs.set(serviceBs.BTCEUR, "Bitcoin Eur"); - this.currpairs.set(serviceBs.ETHEUR, "Ether Eur"); - this.currpairs.set(serviceBs.LTCEUR, "Litecoin Eur"); - this.currpairs.set(serviceBs.XRPEUR, "Ripple Eur"); - this.currpairs.set(serviceBs.BTCUSD, "Bitcoin Usd"); - this.currpairs.set(serviceBs.ETHUSD, "Ether Usd"); - this.currpairs.set(serviceBs.LTCUSD, "Litecoin Usd"); - this.currpairs.set(serviceBs.XRPUSD, "Ripple Usd"); - this.currpairs.set(serviceIb.BTCEUR, "Bitcoin Eur"); - this.currpairs.set(serviceIb.BTCUSD, "Bitcoin Usd"); - this.currpairs.set(serviceCb.BTCUSD, "Bitcoin Usd"); - this.currpairs.set(serviceCb.ETHUSD, "Ether Usd"); - this.currpairs.set(serviceCb.LTCUSD, "Litecoin Usd"); - this.currpairs.set(serviceBf.BTCUSD, "Bitcoin Usd"); - this.currpairs.set(serviceBf.ETHUSD, "Ether Usd"); - this.currpairs.set(serviceBf.LTCUSD, "Litecoin Usd"); - this.currpairs.set(serviceBf.XRPUSD, "Ripple Usd"); - } - - getCurrpairName(key: string) { - return this.currpairs.get(key); - } -} \ No newline at end of file diff --git a/src/angular/trader/src/app/common/orderbookBf.ts b/src/angular/trader/src/app/common/orderbookBf.ts deleted file mode 100644 index b2e08d85..00000000 --- a/src/angular/trader/src/app/common/orderbookBf.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface OrderbookBf { - bids: OrderBf[]; - asks: OrderBf[]; -} - -export interface OrderBf { - price: string; - amount: string; - timestamp: Date; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/common/orderbookBs.ts b/src/angular/trader/src/app/common/orderbookBs.ts deleted file mode 100644 index abcd64e4..00000000 --- a/src/angular/trader/src/app/common/orderbookBs.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface OrderbookBs { - timestamp: Date; - bids: string[][]; - asks: string[][]; -} - diff --git a/src/angular/trader/src/app/common/orderbookIb.ts b/src/angular/trader/src/app/common/orderbookIb.ts deleted file mode 100644 index e5e850d3..00000000 --- a/src/angular/trader/src/app/common/orderbookIb.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface OrderbookIb { - bids: string[][]; - asks: string[][]; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/common/quoteCb.ts b/src/angular/trader/src/app/common/quoteCb.ts deleted file mode 100644 index 61bf86db..00000000 --- a/src/angular/trader/src/app/common/quoteCb.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -export interface QuoteCbSmall { - _id: string; - createdAt: Date; - usd: number; - eur: number; - eth: number; - ltc: number; -} - -export interface QuoteCb { - - _id: string; - createdAt: Date; - aed: number; - afn: number; - all: number; - amd: number; - ang: number; - aoa: number; - ars: number; - aud: number; - awg: number; - azn: number; - bam: number; - bbd: number; - bdt: number; - bgn: number; - bhd: number; - bif: number; - bmd: number; - bnd: number; - bob: number; - brl: number; - bsd: number; - btc: number; - btn: number; - bwp: number; - byn: number; - byr: number; - bzd: number; - cad: number; - cdf: number; - chf: number; - clf: number; - clp: number; - cny: number; - cop: number; - crc: number; - cuc: number; - cve: number; - czk: number; - djf: number; - dkk: number; - dop: number; - dzd: number; - eek: number; - egp: number; - ern: number; - etb: number; - eth: number; - eur: number; - fjd: number; - fkp: number; - gbp: number; - gel: number; - ggp: number; - ghs: number; - gip: number; - gmd: number; - gnf: number; - gtq: number; - gyd: number; - hkd: number; - hnl: number; - hrk: number; - htg: number; - huf: number; - idr: number; - ils: number; - imp: number; - inr: number; - iqd: number; - isk: number; - jep: number; - jmd: number; - jod: number; - jpy: number; - kes: number; - kgs: number; - khr: number; - kmf: number; - krw: number; - kwd: number; - kyd: number; - kzt: number; - lak: number; - lbp: number; - lkr: number; - lrd: number; - lsl: number; - ltc: number; - ltl: number; - lvl: number; - lyd: number; - mad: number; - mdl: number; - mga: number; - mkd: number; - mmk: number; - mnt: number; - mop: number; - mro: number; - mtl: number; - mur: number; - mvr: number; - mwk: number; - mxn: number; - myr: number; - mzn: number; - nad: number; - ngn: number; - nio: number; - nok: number; - npr: number; - nzd: number; - omr: number; - pab: number; - pen: number; - pgk: number; - php: number; - pkr: number; - pln: number; - pyg: number; - qar: number; - ron: number; - rsd: number; - rub: number; - rwf: number; - sar: number; - sbd: number; - scr: number; - sek: number; - sgd: number; - shp: number; - sll: number; - sos: number; - srd: number; - ssp: number; - std: number; - svc: number; - szl: number; - thb: number; - tjs: number; - tmt: number; - tnd: number; - top: number; - try1: number; - ttd: number; - twd: number; - tzs: number; - uah: number; - ugx: number; - usd: number; - uyu: number; - uzs: number; - vef: number; - vnd: number; - vuv: number; - wst: number; - xaf: number; - xag: number; - xau: number; - xcd: number; - xdr: number; - xof: number; - xpd: number; - xpf: number; - xpt: number; - yer: number; - zar: number; - zmk: number; - zmw: number; - zwl: number; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/ibdetail/ibdetail.component.html b/src/angular/trader/src/app/ibdetail/ibdetail.component.html deleted file mode 100644 index 45097cff..00000000 --- a/src/angular/trader/src/app/ibdetail/ibdetail.component.html +++ /dev/null @@ -1,53 +0,0 @@ -
- ItBit {{currPair}} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Last:{{currQuote?.lastPrice | number:'1.2'}}High:{{currQuote?.high24h | number:'1.2'}}Low:{{currQuote?.low24h | number:'1.2'}}
Bid:{{currQuote?.bid | number:'1.2'}}Ask:{{currQuote?.ask | number:'1.2'}}Open:{{currQuote?.openToday | number:'1.2'}}
Vwap:{{currQuote?.vwap24h | number:'1.2'}}Pair:{{currPair}}Timestamp:{{currQuote?.createdAt | date:'HH:mm:ss' }}
Volume:{{currQuote?.volume24h | number:'1.2'}}
- -
- -
-
{{todayQuotes?.length }} -
-
diff --git a/src/angular/trader/src/app/ibdetail/ibdetail.component.scss b/src/angular/trader/src/app/ibdetail/ibdetail.component.scss deleted file mode 100644 index 50b5db28..00000000 --- a/src/angular/trader/src/app/ibdetail/ibdetail.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -td { - width: 150px; -} -.radioGroup { - margin-left: 100px; -} -.radioButton { - margin-left: 10px; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/ibdetail/ibdetail.component.spec.ts b/src/angular/trader/src/app/ibdetail/ibdetail.component.spec.ts deleted file mode 100644 index 039e938f..00000000 --- a/src/angular/trader/src/app/ibdetail/ibdetail.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { IbdetailComponent } from './ibdetail.component'; - -describe('IbdetailComponent', () => { - let component: IbdetailComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ IbdetailComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(IbdetailComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/angular/trader/src/app/ibdetail/ibdetail.component.ts b/src/angular/trader/src/app/ibdetail/ibdetail.component.ts deleted file mode 100644 index 700076b0..00000000 --- a/src/angular/trader/src/app/ibdetail/ibdetail.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router, ParamMap } from '@angular/router'; -import { trigger, state, animate, transition, style } from '@angular/animations'; -import 'rxjs/add/operator/switchMap'; -import { QuoteIb } from '../common/quoteIb'; -import { ItbitService} from '../services/itbit.service'; -import { CommonUtils } from '../common/commonUtils'; - -@Component({ - selector: 'app-ibdetail', - templateUrl: './ibdetail.component.html', - styleUrls: ['./ibdetail.component.scss'], - animations: [ - trigger('showChart', [ - state('true' , style({ opacity: 1 })), - state('false', style({ opacity: 0 })), - transition('1 => 0', animate('300ms')), - transition('0 => 1', animate('300ms')) - ]) - ] -}) -export class IbdetailComponent implements OnInit { - currQuote: QuoteIb; - todayQuotes: QuoteIb[] = []; - chartdata: number[] = []; - chartlabels: string[] = []; - chartType = "line"; - utils = new CommonUtils(); - currPair = ""; - timeframe = this.utils.timeframes[0]; - - constructor(private route: ActivatedRoute, private router: Router, private serviceIb: ItbitService) { } - - ngOnInit() { - this.route.paramMap - .switchMap((params: ParamMap) => { - this.currPair = this.utils.getCurrpairName(params.get('currpair')); - return this.serviceIb.getCurrentQuote(params.get('currpair'));}) - .subscribe(quote => this.currQuote = quote); - this.route.paramMap - .switchMap((params: ParamMap) => this.serviceIb.getTodayQuotes(params.get('currpair'))) - .subscribe(quotes => { - this.todayQuotes = quotes; - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - this.chartdata = this.todayQuotes.map(quote => quote.lastPrice); - }); - } - - changeTf() { - this.chartdata = []; - this.chartlabels = []; - this.route.paramMap - .switchMap((params: ParamMap) => { - if(this.timeframe === this.utils.timeframes[1]) return this.serviceIb.get7DayQuotes(params.get('currpair')); - if(this.timeframe === this.utils.timeframes[2]) return this.serviceIb.get30DayQuotes(params.get('currpair')); - if(this.timeframe === this.utils.timeframes[3]) return this.serviceIb.get90DayQuotes(params.get('currpair')) - else return this.serviceIb.getTodayQuotes(params.get('currpair')); - }) - .subscribe(quotes => { - this.todayQuotes = quotes; - if(this.timeframe === this.utils.timeframes[2] || this.timeframe === this.utils.timeframes[3]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getUTCDate().toString()) - else if(this.timeframe === this.utils.timeframes[1]) - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getDay().toString()) - else - this.chartlabels = this.todayQuotes.map(quote => new Date(quote.createdAt).getHours().toString()); - this.chartdata = this.todayQuotes.map(quote => quote.lastPrice); - }); - } -} diff --git a/src/angular/trader/src/app/login/login.component.html b/src/angular/trader/src/app/login/login.component.html deleted file mode 100644 index aad7d37a..00000000 --- a/src/angular/trader/src/app/login/login.component.html +++ /dev/null @@ -1,70 +0,0 @@ -
- - -
-
    -
  • - - - -
  • -
  • - - - -
  • -
  • - Login Failed -
  • -
  • - - -
  • -
-
-
- -
-
    -
  • - - - -
  • -
  • - - - - - - -
  • -
  • - Passwords do not match -
  • -
  • - - - -
  • -
  • - Signin Failed -
  • -
  • - - -
  • -
-
-
-
-
- - - diff --git a/src/angular/trader/src/app/login/login.component.scss b/src/angular/trader/src/app/login/login.component.scss deleted file mode 100644 index e21b9831..00000000 --- a/src/angular/trader/src/app/login/login.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -ul { - list-style-type: none; -} - -.errorText { - color: red; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/login/login.component.ts b/src/angular/trader/src/app/login/login.component.ts deleted file mode 100644 index f78146dd..00000000 --- a/src/angular/trader/src/app/login/login.component.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Component, OnInit, Inject } from '@angular/core'; -import { QuoteoverviewComponent } from '../quoteoverview/quoteoverview.component'; -import { MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material'; -import { MyuserService } from '../services/myuser.service'; -import { MyUser } from '../common/myUser'; -import { FormGroup, FormControl, FormBuilder, Validators, AbstractControl } from '@angular/forms'; - - -@Component({ - selector: 'app-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.scss'] -}) -export class LoginComponent implements OnInit { - signinForm: FormGroup; - loginForm: FormGroup; - private user = new MyUser(); - loginFailed = false; - signinFailed = false; - pwMatching = true; - - constructor(public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: any, private myuserService: MyuserService, fb: FormBuilder) { - this.signinForm = fb.group({ - username: ['', Validators.required], - password: ['', Validators.required], - password2: ['', Validators.required], - email: ['', Validators.required] - },{ - validator: this.validate.bind(this) - }); - this.loginForm = fb.group({ - username: ['', Validators.required], - password: ['', Validators.required] - }); - } - - ngOnInit() { - - } - - validate(group: FormGroup) { - if(group.get('password').touched || group.get('password2').touched) { - this.pwMatching = group.get('password').value === group.get('password2').value && group.get('password').value !== ''; - if(!this.pwMatching) { - group.get('password').setErrors({MatchPassword: true}); - group.get('password2').setErrors({MatchPassword: true}); - } else { - group.get('password').setErrors(null); - group.get('password2').setErrors(null); - } - } - return this.pwMatching; - } - - onSigninClick(): void { - let myUser = new MyUser(); - myUser.userId = this.signinForm.get('username').value; - myUser.password = this.signinForm.get('password').value; - myUser.email = this.signinForm.get('email').value; -// console.log(this.signinForm); -// console.log(myUser); - this.myuserService.postSignin(myUser).subscribe(us => this.signin(us),err => console.log(err)); - } - - onLoginClick(): void { - let myUser = new MyUser(); - myUser.userId = this.loginForm.get('username').value; - myUser.password = this.loginForm.get('password').value; -// console.log(myUser); - this.myuserService.postLogin(myUser).subscribe(us => this.login(us),err => console.log(err)); - } - - signin(us: MyUser):void { - this.user = us; - this.data.hash = null; - if(this.user.userId !== null) { - this.signinFailed = false; - this.dialogRef.close(); - } else { - this.signinFailed = true; - } - } - - login(us: MyUser):void { - this.user = us; - if(this.user.userId !== null) { - this.loginFailed = false; - this.data.hash = us.salt; - this.dialogRef.close(this.data.hash); - } else { - this.loginFailed = true; - } - } - - onCancelClick(): void { - this.dialogRef.close(); - } -} diff --git a/src/angular/trader/src/app/material.module.ts b/src/angular/trader/src/app/material.module.ts deleted file mode 100644 index 7edf8ed9..00000000 --- a/src/angular/trader/src/app/material.module.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { -// MatAutocompleteModule, - MatButtonModule, -// MatButtonToggleModule, -// MatCardModule, - MatCheckboxModule, -// MatChipsModule, -// MatDatepickerModule, - MatDialogModule, -// MatExpansionModule, -// MatGridListModule, -// MatIconModule, - MatInputModule, - MatListModule, -// MatMenuModule, -// MatNativeDateModule, -// MatPaginatorModule, -// MatProgressBarModule, -// MatProgressSpinnerModule, - MatRadioModule, -// MatRippleModule, - MatSelectModule, -// MatSidenavModule, -// MatSliderModule, -// MatSlideToggleModule, -// MatSnackBarModule, -// MatSortModule, - MatTableModule, - MatTabsModule, - MatToolbarModule, -// MatTooltipModule, -// MatStepperModule, - } from '@angular/material'; - import { NgModule } from '@angular/core'; - import {CdkTableModule} from '@angular/cdk/table'; - - @NgModule({ - exports: [ - CdkTableModule, -// MatAutocompleteModule, - MatButtonModule, -// MatButtonToggleModule, -// MatCardModule, - MatCheckboxModule, -// MatChipsModule, -// MatStepperModule, -// MatDatepickerModule, - MatDialogModule, -// MatExpansionModule, -// MatGridListModule, -// MatIconModule, - MatInputModule, - MatListModule, -// MatMenuModule, -// MatNativeDateModule, -// MatPaginatorModule, -// MatProgressBarModule, -// MatProgressSpinnerModule, - MatRadioModule, -// MatRippleModule, - MatSelectModule, -// MatSidenavModule, -// MatSliderModule, -// MatSlideToggleModule, -// MatSnackBarModule, -// MatSortModule, - MatTableModule, - MatTabsModule, - MatToolbarModule, -// MatTooltipModule, - ], - }) - export class MaterialModule {} \ No newline at end of file diff --git a/src/angular/trader/src/app/orderbooks/orderbooks.component.html b/src/angular/trader/src/app/orderbooks/orderbooks.component.html deleted file mode 100644 index 8d51a5ac..00000000 --- a/src/angular/trader/src/app/orderbooks/orderbooks.component.html +++ /dev/null @@ -1,79 +0,0 @@ -
- - Orderbooks -
-
-
- - - Bitstamp - - - Itbit - - - Bitfinex - - -
- - Buy - Sell - -
-
- - - - - {{curr.name}} - - - - -
-
- - - -
-
-
- - - - - -
-
-
-
- - Bitstamp Orders - Price  -  Amount - - {{order.price}} - {{order.amount}} - - -
-
- - Itbit Orders - Price  -  Amount - - {{order.price}} - {{order.amount}} - - -
-
- - Bitfinex Orders - Price  -  Amount - - {{order.price}} - {{order.amount}} - - -
-
- -
\ No newline at end of file diff --git a/src/angular/trader/src/app/orderbooks/orderbooks.component.scss b/src/angular/trader/src/app/orderbooks/orderbooks.component.scss deleted file mode 100644 index 25401346..00000000 --- a/src/angular/trader/src/app/orderbooks/orderbooks.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.formatMs { - margin-top: 25px; -} - -.flexcontainer { - display: flex; - flex-wrap: wrap; - flex-direction: row; - justify-content: space-between; -} - -.flexchild { - -} \ No newline at end of file diff --git a/src/angular/trader/src/app/orderbooks/orderbooks.component.spec.ts b/src/angular/trader/src/app/orderbooks/orderbooks.component.spec.ts deleted file mode 100644 index 13df7c66..00000000 --- a/src/angular/trader/src/app/orderbooks/orderbooks.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { OrderbooksComponent } from './orderbooks.component'; - -describe('OrderbooksComponent', () => { - let component: OrderbooksComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ OrderbooksComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(OrderbooksComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/angular/trader/src/app/orderbooks/orderbooks.component.ts b/src/angular/trader/src/app/orderbooks/orderbooks.component.ts deleted file mode 100644 index 605767b0..00000000 --- a/src/angular/trader/src/app/orderbooks/orderbooks.component.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Component, OnInit } from '@angular/core'; -import { BitstampService } from '../services/bitstamp.service'; -import { ItbitService } from '../services/itbit.service'; -import { BitfinexService } from '../services/bitfinex.service'; -import { Router } from '@angular/router'; -import { OrderbookBs } from '../common/orderbookBs'; -import { OrderbookBf, OrderBf } from '../common/orderbookBf'; -import { OrderbookIb } from '../common/orderbookIb'; - -@Component( { - selector: 'app-orderbooks', - templateUrl: './orderbooks.component.html', - styleUrls: ['./orderbooks.component.scss'] -} ) -export class OrderbooksComponent implements OnInit { - - orderbookBs: OrderbookBs; - orderbookBf: OrderbookBf; - orderbookIb: OrderbookIb; - public currencies: MyCurr[]; - model = new MyModel( null, false, false, false, 1, null ); - bsOrders: MyOrder[] = []; - bfOrders: MyOrder[] = []; - ibOrders: MyOrder[] = []; - - constructor( private router: Router, - private serviceBs: BitstampService, - private serviceIb: ItbitService, - private serviceBf: BitfinexService ) { } - - ngOnInit() { - this.currencies = [new MyCurr( this.serviceBf.BTCUSD, 'Btc - Usd' ), new MyCurr( this.serviceBf.ETHUSD, 'Eth - Usd' ), new MyCurr( this.serviceBf.LTCUSD, 'Ltc - Usd' ), new MyCurr( this.serviceBf.XRPUSD, 'Xrp - Usd' )]; - } - - onSubmit() { - //console.log( this.model ); - if ( this.model.itbitCb && this.model.currpair === this.serviceBf.BTCUSD ) { - this.serviceIb.getOrderbook( this.serviceIb.BTCUSD ).subscribe( ob => { -// this.orderbookIb = ob; - this.ibOrders = this.filterObIb( ob ); - } ); - } else { - this.ibOrders = []; - } - if ( this.model.bitstampCb ) { - this.serviceBs.getOrderbook( this.model.currpair ).subscribe( ob => { -// this.orderbookBs = ob; - this.bsOrders = this.filterObBs( ob ); - } ); - } else { - this.bsOrders = []; - } - if ( this.model.bitfinexCb ) { - this.serviceBf.getOrderbook( this.model.currpair ).subscribe( ob => { -// this.orderbookBf = ob; - this.bfOrders = this.filterObBf( ob ); - } ); - } else { - this.bfOrders = []; - } - } - - private filterObBs( ob: OrderbookBs ): MyOrder[] { - let myOrders: MyOrder[] = []; - let sum = 0; - let bidAskArr = this.model.buysell === 1 ? ob.asks : ob.bids; - for ( let i = 0; i < bidAskArr.length; i++ ) { - let order = bidAskArr[i]; - myOrders.push( new MyOrder( this.model.buysell, parseFloat( order[0] ), parseFloat( order[1] ), sum > this.model.amount ? "black" : "blue" ) ); - sum += parseFloat( order[1] ); - if ( sum > (this.model.amount * 1.5) ) { - break; - } - } - return myOrders; - } - - private filterObBf( ob: OrderbookBf ): MyOrder[] { - let myOrders: MyOrder[] = []; - let sum = 0; - let bidAskArr = this.model.buysell === 1 ? ob.asks : ob.bids; - for ( let i = 0; i < bidAskArr.length; i++ ) { - let order = bidAskArr[i]; - myOrders.push( new MyOrder( this.model.buysell, parseFloat( order.price ), parseFloat( order.amount ), sum > this.model.amount ? "black" : "blue" ) ); - sum += parseFloat( order.amount ); - if ( sum > (this.model.amount * 1.5) ) { - break; - } - } - return myOrders; - } - - private filterObIb( ob: OrderbookIb ): MyOrder[] { - let myOrders: MyOrder[] = []; - let sum = 0; - let bidAskArr = this.model.buysell === 1 ? ob.asks : ob.bids; - for ( let i = 0; i < bidAskArr.length; i++ ) { - let order = bidAskArr[i]; - myOrders.push( new MyOrder( this.model.buysell, parseFloat( order[0] ), parseFloat( order[1] ), sum > this.model.amount ? "black" : "blue" ) ); - sum += parseFloat( order[1] ); - if ( sum > (this.model.amount * 1.5) ) { - break; - } - } - return myOrders; - } - - onBack() { - this.router.navigateByUrl( "/overview" ); - } - -} - -export class MyOrder { - constructor( public buysell: number, public price: number, public amount: number, public color: string ) { } -} - -export class MyModel { - constructor( public currpair: string, public bitstampCb: boolean, public itbitCb: boolean, public bitfinexCb: boolean, public buysell: number, public amount: number ) { } -} - -export class MyCurr { - constructor( public value: string, public name: string ) { } -} diff --git a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.html b/src/angular/trader/src/app/quoteoverview/quoteoverview.component.html deleted file mode 100644 index 8ce4e740..00000000 --- a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.html +++ /dev/null @@ -1,39 +0,0 @@ -
- - Curreny Table - - - - - -
- - - Exchange - {{element.exchange}} - - - Currency Pair - {{element.currpair}} - - Last - -
{{element.last < 0.01 ? '--' : element.last | number:'1.2'}}
- -
-
- - Volume - {{element.volume < 1 ? '--' : element.volume | number:'1.2'}} - - - -
-
-
\ No newline at end of file diff --git a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.scss b/src/angular/trader/src/app/quoteoverview/quoteoverview.component.scss deleted file mode 100644 index 32f9cb4e..00000000 --- a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.scss +++ /dev/null @@ -1,37 +0,0 @@ - -.popup { - display: none; -} - -.cell:hover { - .popup { - display: block; - width: 160px; - margin-top: -50px; - margin-left: 100px; - background: #FFFFFF; - border-radius: 6px; - padding: 8px 0; - position: absolute; - z-index: 1; - color: blue; - border: solid 1px black; - } - color: blue; - cursor: pointer; -} - -.example-container { - display: flex; - flex-direction: column; -} - -.mat-table { - overflow: auto; -} - -.example-fill-remaining-space { - // This fills the remaining space, by using flexbox. - // Every toolbar row uses a flexbox row layout. - flex: 1 1 auto; -} \ No newline at end of file diff --git a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.spec.ts b/src/angular/trader/src/app/quoteoverview/quoteoverview.component.spec.ts deleted file mode 100644 index 156b61c0..00000000 --- a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { QuoteoverviewComponent } from './quoteoverview.component'; - -describe('QuoteoverviewComponent', () => { - let component: QuoteoverviewComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ QuoteoverviewComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(QuoteoverviewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.ts b/src/angular/trader/src/app/quoteoverview/quoteoverview.component.ts deleted file mode 100644 index 9d0563a0..00000000 --- a/src/angular/trader/src/app/quoteoverview/quoteoverview.component.ts +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { BitstampService } from '../services/bitstamp.service'; -import { CoinbaseService } from '../services/coinbase.service'; -import { ItbitService } from '../services/itbit.service'; -import { BitfinexService } from '../services/bitfinex.service'; -import { QuoteBs } from '../common/quoteBs'; -import { QuoteCb } from '../common/quoteCb'; -import { QuoteIb } from '../common/quoteIb'; -import { QuoteBf } from '../common/quoteBf'; -import { Observable } from 'rxjs/Observable'; -import {DataSource, CollectionViewer} from '@angular/cdk/collections'; -import 'rxjs/add/observable/of'; -import {Subject} from 'rxjs/Subject'; -import { Router } from '@angular/router'; -import { CommonUtils } from "../common/commonUtils"; -import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material'; -import { LoginComponent } from '../login/login.component'; -import { MyuserService } from '../services/myuser.service'; - -@Component({ - selector: 'app-quoteoverview', - templateUrl: './quoteoverview.component.html', - styleUrls: ['./quoteoverview.component.scss'] -}) -export class QuoteoverviewComponent implements OnInit,OnDestroy { - - datasource = new Myds(); - private interval: any; - private utils = new CommonUtils(); - hash = null; - - constructor(private router: Router, - private serviceBs: BitstampService, - private serviceCb: CoinbaseService, - private serviceIb: ItbitService, - private serviceBf: BitfinexService, - private serviceMu: MyuserService, - public dialog: MatDialog) { - } - - ngOnInit() { - this.refreshData(); - if(this.interval){ - clearInterval(this.interval); - } - this.interval = setInterval(() => { - this.refreshData(); - }, 15000); - for(let i = 0;i<17;i++) { - this.datasource.rows.push(new Myrow("","",0,0, null,-1,-1)); - } - this.hash = this.serviceMu.salt; - //console.log(this.hash); - } - - ngOnDestroy(): void { - if(this.interval){ - clearInterval(this.interval); - } - } - - openLoginDialog(): void { - let dialogRef = this.dialog.open(LoginComponent, { - width: '500px', - data: { hash: this.hash} - }); - - dialogRef.afterClosed().subscribe(result => { - this.hash = typeof result === 'undefined' || result === null ? null : result; - }); - } - - logout():void { - this.serviceMu.postLogout(this.hash).subscribe(myUser => this.hash = myUser.salt); - } - - orderbooks(): void { - this.router.navigateByUrl("orderbooks"); - } - - selectedRow(row: Myrow):void { - //console.log(row); - if(row.exchange === 'Bitstamp') { - this.router.navigateByUrl("bsdetail/"+row.pair); - } else if(row.exchange === 'Itbit' && row.pair === 'XBTUSD') { - this.router.navigateByUrl("ibdetail/"+this.serviceIb.BTCUSD); - } else if (row.exchange === 'Itbit' && row.pair === 'XBTEUR') { - this.router.navigateByUrl("ibdetail/"+this.serviceIb.BTCEUR); - } else if(row.exchange === 'Coinbase') { - this.router.navigateByUrl("cbdetail/"+row.pair); - } else if(row.exchange === 'Bitfinex') { - this.router.navigateByUrl("bfdetail/"+row.pair); - } - } - - private refreshData() { - this.serviceBs.getCurrentQuote(this.serviceBs.BTCEUR).subscribe(quote => { - this.datasource.rows[0] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.BTCEUR)); - this.datasource.updateRows();}); - this.serviceBs.getCurrentQuote(this.serviceBs.ETHEUR).subscribe(quote => { - this.datasource.rows[1] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.ETHEUR)); - this.datasource.updateRows();}); - this.serviceBs.getCurrentQuote(this.serviceBs.LTCEUR).subscribe(quote => { - this.datasource.rows[2] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.LTCEUR)); - this.datasource.updateRows();}); - this.serviceBs.getCurrentQuote(this.serviceBs.XRPEUR).subscribe(quote => { - this.datasource.rows[3] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.XRPEUR)); - this.datasource.updateRows();}); - this.serviceBs.getCurrentQuote(this.serviceBs.BTCUSD).subscribe(quote => { - this.datasource.rows[4] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.BTCUSD)); - this.datasource.updateRows();}); - this.serviceBs.getCurrentQuote(this.serviceBs.ETHUSD).subscribe(quote => { - this.datasource.rows[5] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.ETHUSD)); - this.datasource.updateRows();}); - this.serviceBs.getCurrentQuote(this.serviceBs.LTCUSD).subscribe(quote => { - this.datasource.rows[6] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.LTCUSD)); - this.datasource.updateRows();}); - this.serviceBs.getCurrentQuote(this.serviceBs.XRPUSD).subscribe(quote => { - this.datasource.rows[7] = this.createRowBs(quote, "Bitstamp", this.utils.getCurrpairName(this.serviceBs.XRPUSD)); - this.datasource.updateRows();}); - this.serviceIb.getCurrentQuote(this.serviceIb.BTCEUR).subscribe(quote => { - this.datasource.rows[8] = this.createRowIb(quote, "Itbit", this.utils.getCurrpairName(this.serviceIb.BTCEUR)); - this.datasource.updateRows();}); - this.serviceIb.getCurrentQuote(this.serviceIb.BTCUSD).subscribe(quote => { - this.datasource.rows[9] = this.createRowIb(quote, "Itbit", this.utils.getCurrpairName(this.serviceIb.BTCUSD)); - this.datasource.updateRows();}); - this.serviceCb.getCurrentQuote().subscribe(quote => { - let myrows = this.createRowCb(quote); - this.datasource.rows[10] = myrows[0]; - this.datasource.rows[11] = myrows[1]; - this.datasource.rows[12] = myrows[2]; - this.datasource.updateRows();}); - this.serviceBf.getCurrentQuote(this.serviceBf.BTCUSD).subscribe(quote => { - this.datasource.rows[13] = this.createRowBf(quote, "Bitfinex", this.utils.getCurrpairName(this.serviceBf.BTCUSD)); - this.datasource.updateRows();}); - this.serviceBf.getCurrentQuote(this.serviceBf.ETHUSD).subscribe(quote => { - this.datasource.rows[14] = this.createRowBf(quote, "Bitfinex", this.utils.getCurrpairName(this.serviceBf.ETHUSD)); - this.datasource.updateRows();}); - this.serviceBf.getCurrentQuote(this.serviceBf.LTCUSD).subscribe(quote => { - this.datasource.rows[15] = this.createRowBf(quote, "Bitfinex", this.utils.getCurrpairName(this.serviceBf.LTCUSD)); - this.datasource.updateRows();}); - this.serviceBf.getCurrentQuote(this.serviceBf.XRPUSD).subscribe(quote => { - this.datasource.rows[16] = this.createRowBf(quote, "Bitfinex", this.utils.getCurrpairName(this.serviceBf.XRPUSD)); - this.datasource.updateRows();}); - } - - private formatNumber(x: number) : number { - return isNaN(x) ? 0 : Math.round(x*100)/100; - } - - createRowBs(quote: QuoteBs, exchange: string, currpair: string) : Myrow { - return new Myrow(exchange, currpair, this.formatNumber(quote.last), this.formatNumber(quote.volume), quote.pair, this.formatNumber(quote.high), this.formatNumber(quote.low)); - } - - createRowBf(quote: QuoteBf, exchange: string, currpair: string) : Myrow { - return new Myrow(exchange, currpair, this.formatNumber(quote.last_price), this.formatNumber(quote.volume), quote.pair, this.formatNumber(quote.high), this.formatNumber(quote.low)); - } - - createRowCb(quote: QuoteCb) : Myrow[] { - let rows: Myrow[] = []; - rows.push(new Myrow("Coinbase", this.utils.getCurrpairName(this.serviceCb.BTCUSD), this.formatNumber(quote.usd), -1, this.serviceCb.BTCUSD, -1, -1)); - rows.push(new Myrow("Coinbase", this.utils.getCurrpairName(this.serviceCb.ETHUSD), this.formatNumber(quote.usd / quote.eth), -1, this.serviceCb.ETHUSD, -1, -1)); - rows.push(new Myrow("Coinbase", this.utils.getCurrpairName(this.serviceCb.LTCUSD), this.formatNumber(quote.usd / quote.ltc), -1, this.serviceCb.LTCUSD, -1, -1)); - return rows; - } - - createRowIb(quote: QuoteIb, exchange: string, currpair: string) : Myrow { - return new Myrow(exchange, currpair, this.formatNumber(quote.lastPrice), this.formatNumber(quote.volume24h), quote.pair, this.formatNumber(quote.high24h), this.formatNumber(quote.low24h)); - } -} - -export class Myds extends DataSource { - rows: Myrow[] = []; - private subject:Subject = new Subject(); - - updateRows() : void { - this.subject.next(this.rows); - } - - connect(collectionViewer: CollectionViewer): Observable { - return this.subject; - } - disconnect(collectionViewer: CollectionViewer): void { - } -} - -export class Myrow { - constructor(public exchange: string, public currpair: string, public last: number, public volume: number, public pair: string, public high: number, public low: number) { - - } -} diff --git a/src/angular/trader/src/app/services/auth-guard.service.ts b/src/angular/trader/src/app/services/auth-guard.service.ts deleted file mode 100644 index 7469e30e..00000000 --- a/src/angular/trader/src/app/services/auth-guard.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Injectable } from '@angular/core'; -import { CanActivate,ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router'; -import { Observable } from 'rxjs/Observable'; -import { MyuserService } from './myuser.service'; - -@Injectable() -export class AuthGuardService implements CanActivate { - - constructor(private myuserService: MyuserService) { } - - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable | Promise { - return this.myuserService.postCheckAuthorisation(state.url).map(res => res.authorized); - } -} diff --git a/src/angular/trader/src/app/services/bitfinex.service.ts b/src/angular/trader/src/app/services/bitfinex.service.ts deleted file mode 100644 index cc03bf94..00000000 --- a/src/angular/trader/src/app/services/bitfinex.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { PlatformLocation } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/debounceTime'; -import 'rxjs/add/observable/throw'; -import { QuoteBf } from '../common/quoteBf'; -import { Utils } from './utils'; -import { OrderbookBf } from '../common/orderbookBf'; - -@Injectable() -export class BitfinexService { - private _reqOptionsArgs = { headers: new HttpHeaders().set( 'Content-Type', 'application/json' ) }; - private readonly _bitfinex = '/bitfinex'; - BTCUSD = 'btcusd'; - ETHUSD = 'ethusd'; - LTCUSD = 'ltcusd'; - XRPUSD = 'xrpusd'; - - private _utils = new Utils(); - - constructor(private http: HttpClient, private pl: PlatformLocation ) { - } - - getCurrentQuote(currencypair: string): Observable { - return this.http.get(this._bitfinex+'/'+currencypair+'/current', this._reqOptionsArgs).catch(this._utils.handleError); - } - - getTodayQuotes(currencypair: string): Observable { - return this.http.get(this._bitfinex+'/'+currencypair+'/today', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get7DayQuotes(currencypair: string): Observable { - return this.http.get(this._bitfinex+'/'+currencypair+'/7days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get30DayQuotes(currencypair: string): Observable { - return this.http.get(this._bitfinex+'/'+currencypair+'/30days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get90DayQuotes(currencypair: string): Observable { - return this.http.get(this._bitfinex+'/'+currencypair+'/90days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - getOrderbook(currencypair: string): Observable { - return this.http.get(this._bitfinex+'/'+currencypair+'/orderbook/', this._reqOptionsArgs).catch(this._utils.handleError); - } -} diff --git a/src/angular/trader/src/app/services/bitstamp.service.ts b/src/angular/trader/src/app/services/bitstamp.service.ts deleted file mode 100644 index 39e4007c..00000000 --- a/src/angular/trader/src/app/services/bitstamp.service.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { PlatformLocation } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/debounceTime'; -import 'rxjs/add/observable/throw'; -import { QuoteBs } from '../common/quoteBs'; -import { QuoteCb } from '../common/quoteCb'; -import { QuoteIb } from '../common/quoteIb'; -import { Utils } from './utils'; -import { OrderbookBs } from '../common/orderbookBs'; - -@Injectable() -export class BitstampService { - - private _reqOptionsArgs = { headers: new HttpHeaders().set( 'Content-Type', 'application/json' ) }; - private readonly _bitstamp = '/bitstamp'; - BTCEUR = 'btceur'; - ETHEUR = 'etheur'; - LTCEUR = 'ltceur'; - XRPEUR = 'xrpeur'; - BTCUSD = 'btcusd'; - ETHUSD = 'ethusd'; - LTCUSD = 'ltcusd'; - XRPUSD = 'xrpusd'; - private _utils = new Utils(); - - constructor(private http: HttpClient, private pl: PlatformLocation ) { - } - - getCurrentQuote(currencypair: string): Observable { - return this.http.get(this._bitstamp+'/'+currencypair+'/current', this._reqOptionsArgs).catch(this._utils.handleError); - } - - getTodayQuotes(currencypair: string): Observable { - return this.http.get(this._bitstamp+'/'+currencypair+'/today', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get7DayQuotes(currencypair: string): Observable { - return this.http.get(this._bitstamp+'/'+currencypair+'/7days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get30DayQuotes(currencypair: string): Observable { - return this.http.get(this._bitstamp+'/'+currencypair+'/30days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get90DayQuotes(currencypair: string): Observable { - return this.http.get(this._bitstamp+'/'+currencypair+'/90days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - getOrderbook(currencypair: string): Observable { - return this.http.get(this._bitstamp+'/'+currencypair+'/orderbook/', this._reqOptionsArgs).catch(this._utils.handleError); - } -} diff --git a/src/angular/trader/src/app/services/coinbase.service.ts b/src/angular/trader/src/app/services/coinbase.service.ts deleted file mode 100644 index 2bc69752..00000000 --- a/src/angular/trader/src/app/services/coinbase.service.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { PlatformLocation } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/debounceTime'; -import 'rxjs/add/observable/throw'; -import { QuoteBs } from '../common/quoteBs'; -import { QuoteCbSmall, QuoteCb } from '../common/quoteCb'; -import { QuoteIb } from '../common/quoteIb'; -import { Utils } from './utils'; - -@Injectable() -export class CoinbaseService { - private _reqOptionsArgs= { headers: new HttpHeaders().set( 'Content-Type', 'application/json' ) }; - private readonly _coinbase = '/coinbase'; - private _utils = new Utils(); - BTCUSD = 'btcusd'; - ETHUSD = 'ethusd'; - LTCUSD = 'ltcusd'; - - constructor(private http: HttpClient, private pl: PlatformLocation ) { - } - - getCurrentQuote(): Observable { - return this.http.get(this._coinbase+'/current', this._reqOptionsArgs).map(res => this.lowercaseKeys(res)).catch(this._utils.handleError); - } - - getTodayQuotes(): Observable { - return this.http.get(this._coinbase+'/today', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get7DayQuotes(): Observable { - return this.http.get(this._coinbase+'/7days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get30DayQuotes(): Observable { - return this.http.get(this._coinbase+'/30days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get90DayQuotes(): Observable { - return this.http.get(this._coinbase+'/90days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - private lowercaseKeys(quote: QuoteCb): QuoteCb { - for (let p in quote) { - if( quote.hasOwnProperty(p) && p !== '_id' && p !== 'createdAt') { - quote[p.toLowerCase()] = quote[p]; - //console.log( p + " , " + quote[p] + "\n"); - //console.log( p.toLowerCase() + " , " + quote[p.toLowerCase()] + "\n"); - } - } - return quote; - } -} diff --git a/src/angular/trader/src/app/services/itbit.service.ts b/src/angular/trader/src/app/services/itbit.service.ts deleted file mode 100644 index a11083a4..00000000 --- a/src/angular/trader/src/app/services/itbit.service.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { PlatformLocation } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/debounceTime'; -import 'rxjs/add/observable/throw'; -import { QuoteBs } from '../common/quoteBs'; -import { QuoteCb } from '../common/quoteCb'; -import { QuoteIb } from '../common/quoteIb'; -import { Utils } from './utils'; -import { OrderbookIb } from '../common/orderbookIb'; - -@Injectable() -export class ItbitService { - private _reqOptionsArgs= { headers: new HttpHeaders().set( 'Content-Type', 'application/json' ) }; - private readonly _itbit = '/itbit'; - private _utils = new Utils(); - BTCEUR = 'btceur'; - BTCUSD = 'btcusd'; - - constructor(private http: HttpClient, private pl: PlatformLocation ) { - } - - getCurrentQuote(currencypair: string): Observable { - return this.http.get(this._itbit+'/'+currencypair+'/current', this._reqOptionsArgs).catch(this._utils.handleError); - } - - getTodayQuotes(currencypair: string): Observable { - return this.http.get(this._itbit+'/'+currencypair+'/today', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get7DayQuotes(currencypair: string): Observable { - return this.http.get(this._itbit+'/'+currencypair+'/7days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get30DayQuotes(currencypair: string): Observable { - return this.http.get(this._itbit+'/'+currencypair+'/30days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - get90DayQuotes(currencypair: string): Observable { - return this.http.get(this._itbit+'/'+currencypair+'/90days', this._reqOptionsArgs).catch(this._utils.handleError); - } - - getOrderbook(currencypair: string): Observable { - return this.http.get(this._itbit+'/'+currencypair+'/orderbook/', this._reqOptionsArgs).catch(this._utils.handleError); - } -} diff --git a/src/angular/trader/src/app/services/myuser.service.ts b/src/angular/trader/src/app/services/myuser.service.ts deleted file mode 100644 index f502a931..00000000 --- a/src/angular/trader/src/app/services/myuser.service.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { PlatformLocation } from '@angular/common'; -import { Observable } from 'rxjs/Observable'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/debounceTime'; -import 'rxjs/add/observable/throw'; -import { MyUser } from '../common/myUser'; -import { Utils } from './utils'; -import { AuthCheck } from '../common/authcheck'; - -@Injectable() -export class MyuserService { - private _reqOptionsArgs= { headers: new HttpHeaders().set( 'Content-Type', 'application/json' ) }; - private _utils = new Utils(); - private myUserUrl = "/myuser"; - - constructor(private http: HttpClient, private pl: PlatformLocation ) { - } - - postLogin(user: MyUser): Observable { - return this.http.post(this.myUserUrl+'/login', user, this._reqOptionsArgs).map(res => { - let retval = res; - localStorage.setItem("salt", retval.salt); - return retval; - }).catch(this._utils.handleError); - } - - postSignin(user: MyUser): Observable { - return this.http.post(this.myUserUrl+'/signin', user, this._reqOptionsArgs).map(res => { - let retval = res; - retval.salt = 'xxx'; - retval.password = 'yyy'; - return retval; - }).catch(this._utils.handleError); - } - - postCheckAuthorisation(path: string): Observable { - let authcheck = new AuthCheck(); - authcheck.hash = this.salt; - authcheck.path = path; - return this.http.post(this.myUserUrl+'/authorize', authcheck, this._reqOptionsArgs).catch(this._utils.handleError); - } - - postLogout(hash: string): Observable { - localStorage.clear(); - return this.http.post(this.myUserUrl+'/logout', hash, this._reqOptionsArgs).catch(this._utils.handleError); - } - - get salt():string { - return !localStorage.getItem("salt") ? null : localStorage.getItem("salt"); - } -} diff --git a/src/angular/trader/src/app/services/utils.ts b/src/angular/trader/src/app/services/utils.ts deleted file mode 100644 index b6e3c3cc..00000000 --- a/src/angular/trader/src/app/services/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -import { Http, Response, RequestOptionsArgs, Headers } from '@angular/http'; -import { Observable } from 'rxjs/Observable'; - -export class Utils { - - handleError( error: Response ) { - // in a real world app, we may send the error to some remote logging infrastructure - // instead of just logging it to the console - console.error( JSON.stringify( error ) ); - return Observable.throw( error ); - } - -} \ No newline at end of file diff --git a/src/angular/trader/src/index.html b/src/angular/trader/src/index.html deleted file mode 100644 index c1d744fb..00000000 --- a/src/angular/trader/src/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Trader - - - - - - - - - - diff --git a/src/angular/trader/src/polyfills.ts b/src/angular/trader/src/polyfills.ts deleted file mode 100644 index 6e9f3385..00000000 --- a/src/angular/trader/src/polyfills.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** IE9, IE10 and IE11 requires all of the following polyfills. **/ - import 'core-js/es6/symbol'; - import 'core-js/es6/object'; - import 'core-js/es6/function'; - import 'core-js/es6/parse-int'; - import 'core-js/es6/parse-float'; - import 'core-js/es6/number'; - import 'core-js/es6/math'; - import 'core-js/es6/string'; - import 'core-js/es6/date'; - import 'core-js/es6/array'; - import 'core-js/es6/regexp'; - import 'core-js/es6/map'; - import 'core-js/es6/weak-map'; - import 'core-js/es6/set'; - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** Evergreen browsers require these. **/ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; - - -/** - * Required to support Web Animations `@angular/animation`. - * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation - **/ - import 'web-animations-js'; // Run `npm install --save web-animations-js`. - - - -/*************************************************************************************************** - * Zone JS is required by Angular itself. - */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ - -/** - * Date, currency, decimal and percent pipes. - * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 - */ -// import 'intl'; // Run `npm install --save intl`. -/** - * Need to import at least one locale-data with intl. - */ -// import 'intl/locale-data/jsonp/en'; diff --git a/src/angular/trader/src/styles.scss b/src/angular/trader/src/styles.scss deleted file mode 100644 index 375001a2..00000000 --- a/src/angular/trader/src/styles.scss +++ /dev/null @@ -1,11 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ -@import '~@angular/material/prebuilt-themes/deeppurple-amber.css'; - -.detailLinks { - margin-top: 10px; - margin-bottom: 10px; -} - -.currPair { - padding-left: 10px; -} \ No newline at end of file diff --git a/src/angular/trader/src/test.ts b/src/angular/trader/src/test.ts deleted file mode 100644 index 07fed98a..00000000 --- a/src/angular/trader/src/test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/dist/long-stack-trace-zone'; -import 'zone.js/dist/proxy.js'; -import 'zone.js/dist/sync-test'; -import 'zone.js/dist/jasmine-patch'; -import 'zone.js/dist/async-test'; -import 'zone.js/dist/fake-async-test'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting -} from '@angular/platform-browser-dynamic/testing'; - -// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. -declare const __karma__: any; -declare const require: any; - -// Prevent Karma from running prematurely. -__karma__.loaded = function () {}; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting() -); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); -// Finally, start Karma to run the tests. -__karma__.start(); \ No newline at end of file diff --git a/src/angular/trader/src/tsconfig.app.json b/src/angular/trader/src/tsconfig.app.json deleted file mode 100644 index 56e420db..00000000 --- a/src/angular/trader/src/tsconfig.app.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/app", - "baseUrl": "./", - "module": "es2015", - "target": "es5", - "types": [] - }, - "exclude": [ - "test.ts", - "**/*.spec.ts" - ] -} diff --git a/src/angular/trader/src/tsconfig.spec.json b/src/angular/trader/src/tsconfig.spec.json deleted file mode 100644 index 63d89ff2..00000000 --- a/src/angular/trader/src/tsconfig.spec.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/spec", - "baseUrl": "./", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "node" - ] - }, - "files": [ - "test.ts" - ], - "include": [ - "**/*.spec.ts", - "**/*.d.ts" - ] -} diff --git a/src/angular/trader/tsconfig.json b/src/angular/trader/tsconfig.json deleted file mode 100644 index a6c016bf..00000000 --- a/src/angular/trader/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] - } -} diff --git a/src/angular/trader/tslint.json b/src/angular/trader/tslint.json deleted file mode 100644 index 9963d6c3..00000000 --- a/src/angular/trader/tslint.json +++ /dev/null @@ -1,143 +0,0 @@ -{ - "rulesDirectory": [ - "node_modules/codelyzer" - ], - "rules": { - "arrow-return-shorthand": true, - "callable-types": true, - "class-name": true, - "comment-format": [ - true, - "check-space" - ], - "curly": true, - "deprecation": { - "severity": "warn" - }, - "eofline": true, - "forin": true, - "import-blacklist": [ - true, - "rxjs", - "rxjs/Rx" - ], - "import-spacing": true, - "indent": [ - true, - "spaces" - ], - "interface-over-type-literal": true, - "label-position": true, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-arg": true, - "no-bitwise": true, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-super": true, - "no-empty": false, - "no-empty-interface": true, - "no-eval": true, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-misused-new": true, - "no-non-null-assertion": true, - "no-shadowed-variable": true, - "no-string-literal": false, - "no-string-throw": true, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unnecessary-initializer": true, - "no-unused-expression": true, - "no-use-before-declare": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" - ], - "prefer-const": true, - "quotemark": [ - true, - "single" - ], - "radix": true, - "semicolon": [ - true, - "always" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "unified-signatures": true, - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ], - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], - "no-output-on-prefix": true, - "use-input-property-decorator": true, - "use-output-property-decorator": true, - "use-host-property-decorator": true, - "no-input-rename": true, - "no-output-rename": true, - "use-life-cycle-interface": true, - "use-pipe-transform-interface": true, - "component-class-suffix": true, - "directive-class-suffix": true - } -} diff --git a/src/main/java/ch/xxx/trader/BitfinexController.java b/src/main/java/ch/xxx/trader/BitfinexController.java deleted file mode 100644 index 5f1672cb..00000000 --- a/src/main/java/ch/xxx/trader/BitfinexController.java +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader; - -import java.util.Optional; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.client.WebClient; - -import ch.xxx.trader.data.PrepareData; -import ch.xxx.trader.dtos.QuoteBf; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -@RestController -@RequestMapping("/bitfinex") -public class BitfinexController { - private static final String URLBF = "https://api.bitfinex.com"; - - @Autowired - private ReactiveMongoOperations operations; - - @GetMapping("/{currpair}/orderbook") - public Mono getOrderbook(@PathVariable String currpair, HttpServletRequest request) { - if (!WebUtils.checkOBRequest(request, WebUtils.LASTOBCALLBF)) { - return Mono.just("{\n" + " \"bids\":[],\n" + " \"asks\":[]\n" + "}"); - } - WebClient wc = WebUtils.buildWebClient(URLBF); - return wc.get().uri("/v1/book/" + currpair + "/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(res -> res.bodyToMono(String.class)); - } - - @GetMapping - public Flux allQuotes() { - return this.operations.findAll(QuoteBf.class); - } - - @GetMapping("/{pair}/current") - public Mono currentQuote(@PathVariable String pair) { - Query query = MongoUtils.buildCurrentQuery(Optional.of(pair)); - return this.operations.findOne(query, QuoteBf.class); - } - - @GetMapping("/{pair}/{timeFrame}") - public Flux tfQuotes(@PathVariable String timeFrame, @PathVariable String pair) { - if (MongoUtils.TimeFrame.TODAY.getValue().equals(timeFrame)) { - Query query = MongoUtils.buildTodayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBf.class).filter(q -> filterEvenMinutes(q)); - } else if (MongoUtils.TimeFrame.SEVENDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build7DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBf.class, PrepareData.BF_HOUR_COL); - } else if (MongoUtils.TimeFrame.THIRTYDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build30DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBf.class, PrepareData.BF_DAY_COL); - } else if (MongoUtils.TimeFrame.NINTYDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build90DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBf.class, PrepareData.BF_DAY_COL); - } - - return Flux.empty(); - } - - @GetMapping("/btcusd") - public Flux allQuotesBtcUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("btcusd")); - return this.operations.find(query, QuoteBf.class); - } - - @GetMapping("/ethusd") - public Flux allQuotesEthUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("ethusd")); - return this.operations.find(query, QuoteBf.class); - } - - @GetMapping("/ltcusd") - public Flux allQuotesLtcUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("ltcusd")); - return this.operations.find(query, QuoteBf.class); - } - - @GetMapping("/xrpusd") - public Flux allQuotesXrpUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("xrpusd")); - return this.operations.find(query, QuoteBf.class); - } - - private boolean filterEvenMinutes(QuoteBf quote) { - return MongoUtils.filterEvenMinutes(quote.getCreatedAt()); - } -} diff --git a/src/main/java/ch/xxx/trader/BitstampController.java b/src/main/java/ch/xxx/trader/BitstampController.java deleted file mode 100644 index 7de7269c..00000000 --- a/src/main/java/ch/xxx/trader/BitstampController.java +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader; - -import java.util.Optional; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.client.WebClient; - -import ch.xxx.trader.data.PrepareData; -import ch.xxx.trader.dtos.QuoteBs; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -@RestController -@RequestMapping("/bitstamp") -public class BitstampController { - private static final String URLBS = "https://www.bitstamp.net/api"; - - @Autowired - private ReactiveMongoOperations operations; - - @GetMapping("/{currpair}/orderbook") - public Mono getOrderbook(@PathVariable String currpair, HttpServletRequest request) { - if (!WebUtils.checkOBRequest(request, WebUtils.LASTOBCALLBS)) { - return Mono.just("{\"timestamp\": \"\", \"bids\": [], \"asks\": [] }"); - } - WebClient wc = WebUtils.buildWebClient(URLBS); - return wc.get().uri("/v2/order_book/" + currpair + "/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(res -> res.bodyToMono(String.class)); - } - - @GetMapping - public Flux allQuotes() { - return this.operations.findAll(QuoteBs.class); - } - - @GetMapping("/{pair}/current") - public Mono currentQuoteBtc(@PathVariable String pair) { - Query query = MongoUtils.buildCurrentQuery(Optional.of(pair)); - return this.operations.findOne(query, QuoteBs.class); - } - - @GetMapping("/{pair}/{timeFrame}") - public Flux tfQuotesBtc(@PathVariable String timeFrame, @PathVariable String pair) { - if (MongoUtils.TimeFrame.TODAY.getValue().equals(timeFrame)) { - Query query = MongoUtils.buildTodayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBs.class).filter(q -> filterEvenMinutes(q)); - } else if(MongoUtils.TimeFrame.SEVENDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build7DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBs.class, PrepareData.BS_HOUR_COL); - } else if(MongoUtils.TimeFrame.THIRTYDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build30DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBs.class, PrepareData.BS_DAY_COL); - } else if(MongoUtils.TimeFrame.NINTYDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build90DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteBs.class, PrepareData.BS_DAY_COL); - } - - return Flux.empty(); - } - - @GetMapping("/btceur") - public Flux allQuotesBtc() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("btceur")); - return this.operations.find(query, QuoteBs.class); - } - - @GetMapping("/etheur") - public Flux allQuotesEth() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("etheur")); - return this.operations.find(query, QuoteBs.class); - } - - @GetMapping("/ltceur") - public Flux allQuotesLtc() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("ltceur")); - return this.operations.find(query, QuoteBs.class); - } - - @GetMapping("/xrpeur") - public Flux allQuotesXrp() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("xrpeur")); - return this.operations.find(query, QuoteBs.class); - } - - @GetMapping("/btcusd") - public Flux allQuotesBtcUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("btcusd")); - return this.operations.find(query, QuoteBs.class); - } - - @GetMapping("/ethusd") - public Flux allQuotesEthUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("ethusd")); - return this.operations.find(query, QuoteBs.class); - } - - @GetMapping("/ltcusd") - public Flux allQuotesLtcUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("ltcusd")); - return this.operations.find(query, QuoteBs.class); - } - - @GetMapping("/xrpusd") - public Flux allQuotesXrpUsd() { - Query query = new Query(); - query.addCriteria(Criteria.where("pair").is("xrpusd")); - return this.operations.find(query, QuoteBs.class); - } - - private boolean filterEvenMinutes(QuoteBs quote) { - return MongoUtils.filterEvenMinutes(quote.getCreatedAt()); - } -} diff --git a/src/main/java/ch/xxx/trader/CoinbaseController.java b/src/main/java/ch/xxx/trader/CoinbaseController.java deleted file mode 100644 index 916c7796..00000000 --- a/src/main/java/ch/xxx/trader/CoinbaseController.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader; - -import java.util.Optional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import ch.xxx.trader.data.PrepareData; -import ch.xxx.trader.dtos.QuoteCb; -import ch.xxx.trader.dtos.QuoteCbSmall; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -@RestController -@RequestMapping("/coinbase") -public class CoinbaseController { - - @Autowired - private ReactiveMongoOperations operations; - - @GetMapping - public Flux allQuotes() { - return this.operations.findAll(QuoteCb.class); - } - - @GetMapping("/today") - public Flux todayQuotesBc() { - Query query = MongoUtils.buildTodayQuery(Optional.empty()); - return this.operations.find(query,QuoteCb.class) - .filter(q -> filterEvenMinutes(q)) - .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), quote.getLtc())); - } - - @GetMapping("/7days") - public Flux sevenDaysQuotesBc() { - Query query = MongoUtils.build7DayQuery(Optional.empty()); - return this.operations.find(query,QuoteCb.class,PrepareData.CB_HOUR_COL) - .filter(q -> filterEvenMinutes(q)) - .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), quote.getLtc())); - } - - @GetMapping("/30days") - public Flux thirtyDaysQuotesBc() { - Query query = MongoUtils.build30DayQuery(Optional.empty()); - return this.operations.find(query,QuoteCb.class,PrepareData.CB_DAY_COL) - .filter(q -> filterEvenMinutes(q)) - .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), quote.getLtc())); - } - - @GetMapping("/90days") - public Flux nintyDaysQuotesBc() { - Query query = MongoUtils.build90DayQuery(Optional.empty()); - return this.operations.find(query,QuoteCb.class,PrepareData.CB_DAY_COL) - .filter(q -> filterEvenMinutes(q)) - .map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), quote.getLtc())); - } - - @GetMapping("/current") - public Mono currentQuoteBc() { - Query query = MongoUtils.buildCurrentQuery(Optional.empty()); - return this.operations.findOne(query,QuoteCb.class); - } - - private boolean filterEvenMinutes(QuoteCb quote) { - return MongoUtils.filterEvenMinutes(quote.getCreatedAt()); - } -} diff --git a/src/main/java/ch/xxx/trader/ItbitController.java b/src/main/java/ch/xxx/trader/ItbitController.java deleted file mode 100644 index ab40e83b..00000000 --- a/src/main/java/ch/xxx/trader/ItbitController.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.client.WebClient; - -import ch.xxx.trader.data.PrepareData; -import ch.xxx.trader.dtos.QuoteIb; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -@RestController -@RequestMapping("/itbit") -public class ItbitController { - private static final String URLIB = "https://api.itbit.com"; - private final Map currpairs = new HashMap(); - - @Autowired - private ReactiveMongoOperations operations; - - public ItbitController() { - this.currpairs.put("btcusd", "XBTUSD"); - this.currpairs.put("btceur", "XBTEUR"); - } - - @GetMapping("/{currpair}/orderbook") - public Mono getOrderbook(@PathVariable String currpair, HttpServletRequest request) { - if(!WebUtils.checkOBRequest(request, WebUtils.LASTOBCALLIB)) { - return Mono.just("{\"bids\": [], \"asks\": [] }"); - } - currpair = currpair.equals("btcusd") ? "XBTUSD" : currpair; - WebClient wc = WebUtils.buildWebClient(URLIB); - return wc.get().uri("/v1/markets/"+currpair+"/order_book/").accept(MediaType.APPLICATION_JSON).exchange().flatMap(res -> res.bodyToMono(String.class)); - } - - @GetMapping - public Flux allQuotes() { - return this.operations.findAll(QuoteIb.class); - } - - @GetMapping("/{pair}/current") - public Mono currentQuote(@PathVariable String pair) { - pair = this.currpairs.get(pair); - Query query = MongoUtils.buildCurrentQuery(Optional.of(pair)); - return this.operations.findOne(query, QuoteIb.class); - } - - @GetMapping("/{pair}/{timeFrame}") - public Flux tfQuotes(@PathVariable String timeFrame, @PathVariable String pair) { - pair = this.currpairs.get(pair); - if (MongoUtils.TimeFrame.TODAY.getValue().equals(timeFrame)) { - Query query = MongoUtils.buildTodayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteIb.class).filter(q -> filterEvenMinutes(q)); - } else if (MongoUtils.TimeFrame.SEVENDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build7DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteIb.class, PrepareData.IB_HOUR_COL); - } else if (MongoUtils.TimeFrame.THIRTYDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build30DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteIb.class, PrepareData.IB_DAY_COL); - } else if (MongoUtils.TimeFrame.NINTYDAYS.getValue().equals(timeFrame)) { - Query query = MongoUtils.build90DayQuery(Optional.of(pair)); - return this.operations.find(query, QuoteIb.class, PrepareData.IB_DAY_COL); - } - - return Flux.empty(); - } - - private boolean filterEvenMinutes(QuoteIb quote) { - return MongoUtils.filterEvenMinutes(quote.getCreatedAt()); - } -} diff --git a/src/main/java/ch/xxx/trader/MongoUtils.java b/src/main/java/ch/xxx/trader/MongoUtils.java deleted file mode 100644 index a4ec1a0a..00000000 --- a/src/main/java/ch/xxx/trader/MongoUtils.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Optional; - -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; - -public class MongoUtils { - - public enum TimeFrame { - CURRENT("current"), TODAY("today"), SEVENDAYS("7days"), THIRTYDAYS("30days"), NINTYDAYS("90days"); - private TimeFrame(String value) { - this.value = value; - } - - private String value; - - public String getValue() { - return this.value; - } - }; - - private static final Query buildQuery(Optional pair, boolean ascending, Optional begin) { - Calendar cal = GregorianCalendar.getInstance(); - cal.add(Calendar.DAY_OF_YEAR, -1); - Query query = new Query(); - if (pair.isPresent()) { - query.addCriteria(Criteria.where("pair").is(pair.get())); - } - if(begin.isPresent()) { - query.addCriteria(Criteria.where("createdAt").gt(begin.get().getTime())); - } else { - query.addCriteria(Criteria.where("createdAt").gt(cal.getTime())); - } - if (ascending) { - query.with(Sort.by("createdAt").ascending()); - } else { - query.with(Sort.by("createdAt").descending()); - } - return query; - } - - public static final Query build90DayQuery(Optional pair) { - Calendar cal = GregorianCalendar.getInstance(); - cal.add(Calendar.DAY_OF_YEAR, -90); - return buildQuery(pair, true, Optional.of(cal)); - } - - public static final Query build30DayQuery(Optional pair) { - Calendar cal = GregorianCalendar.getInstance(); - cal.add(Calendar.DAY_OF_YEAR, -30); - return buildQuery(pair, true, Optional.of(cal)); - } - - public static final Query build7DayQuery(Optional pair) { - Calendar cal = GregorianCalendar.getInstance(); - cal.add(Calendar.DAY_OF_YEAR, -7); - return buildQuery(pair, true, Optional.of(cal)); - } - - public static final Query buildTodayQuery(Optional pair) { - return buildQuery(pair, true, Optional.empty()); - } - - public static final Query buildCurrentQuery(Optional pair) { - return buildQuery(pair, false, Optional.empty()); - } - - public static final boolean filterEvenMinutes(Date date) { - return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).getMinute() % 2 == 0; - } -} diff --git a/src/main/java/ch/xxx/trader/MyAuthenticationProvider.java b/src/main/java/ch/xxx/trader/MyAuthenticationProvider.java deleted file mode 100644 index 3c61decd..00000000 --- a/src/main/java/ch/xxx/trader/MyAuthenticationProvider.java +++ /dev/null @@ -1,56 +0,0 @@ -package ch.xxx.trader; - -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.stereotype.Component; - -import ch.xxx.trader.dtos.MyUser; - -@Component -public class MyAuthenticationProvider implements AuthenticationProvider { - private static final Logger log = LoggerFactory.getLogger(MyAuthenticationProvider.class); - @Autowired - private ReactiveMongoOperations operations; - @Autowired - private PasswordEncryption passwordEncryption; - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - String name = authentication.getName(); - String password = authentication.getCredentials().toString(); - Query query = new Query(); - query.addCriteria(Criteria.where("userId").is(name)); - MyUser user = operations.findOne(query, MyUser.class).block(); - String encryptedPw = null; - try { - encryptedPw = this.passwordEncryption.getEncryptedPassword(password, user.getSalt()); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - log.error("Pw decrytion error: ",e); - } - if(encryptedPw == null || !encryptedPw.equals(user.getPassword())) { - throw new AuthenticationCredentialsNotFoundException("User: "+name+" not found."); - } - log.info("User: "+name+" logged in."); - return new UsernamePasswordAuthenticationToken( - name, password, user.getAuthorities()); - } - - @Override - public boolean supports(Class authentication) { - return authentication.equals( - UsernamePasswordAuthenticationToken.class); - } - -} diff --git a/src/main/java/ch/xxx/trader/MyUrlRewriteFilter.java b/src/main/java/ch/xxx/trader/MyUrlRewriteFilter.java deleted file mode 100644 index b7f99510..00000000 --- a/src/main/java/ch/xxx/trader/MyUrlRewriteFilter.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader; - -import java.io.IOException; - -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; -import org.springframework.stereotype.Component; -import org.tuckey.web.filters.urlrewrite.Conf; -import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter; - -@Component -public class MyUrlRewriteFilter extends UrlRewriteFilter { - - private static final String CONFIG_LOCATION = "classpath:/urlrewrite.xml"; - - @Value(CONFIG_LOCATION) - private Resource resource; - - @Override - protected void loadUrlRewriter(FilterConfig filterConfig) throws ServletException { - try { - Conf conf = new Conf(filterConfig.getServletContext(), resource.getInputStream(), resource.getFilename(), ""); - checkConf(conf); - } catch (IOException ex) { - throw new ServletException("Unable to load URL-rewrite configuration file from " + CONFIG_LOCATION, ex); - } - } -} diff --git a/src/main/java/ch/xxx/trader/MyUserController.java b/src/main/java/ch/xxx/trader/MyUserController.java deleted file mode 100644 index f88fe643..00000000 --- a/src/main/java/ch/xxx/trader/MyUserController.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader; - -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import ch.xxx.trader.dtos.AuthCheck; -import ch.xxx.trader.dtos.MyUser; -import reactor.core.publisher.Mono; - -@RestController -@RequestMapping("/myuser") -public class MyUserController { - @Autowired - private ReactiveMongoOperations operations; - @Autowired - private PasswordEncryption passwordEncryption; - - @PostMapping("/authorize") - public Mono postAuthorize(@RequestBody AuthCheck authcheck,HttpServletRequest request, - HttpServletResponse response) { - Query query = new Query(); - query.addCriteria(Criteria.where("salt").is(authcheck.getHash())); - return this.operations.findOne(query, MyUser.class).switchIfEmpty(Mono.just(new MyUser())) - .map(user -> mapMyUser(user, authcheck)); - } - - private AuthCheck mapMyUser(MyUser myUser, AuthCheck authcheck) { - if (myUser.getUserId() != null) { - return new AuthCheck(authcheck.getHash(), authcheck.getPath(), true); - } - return new AuthCheck(authcheck.getHash(), authcheck.getPath(), false); - } - - @PostMapping("/signin") - public Mono postUserSignin(@RequestBody MyUser myUser) - throws NoSuchAlgorithmException, InvalidKeySpecException { - Query query = new Query(); - query.addCriteria(Criteria.where("userId").is(myUser.getUserId())); - MyUser user = this.operations.findOne(query, MyUser.class).switchIfEmpty(Mono.just(new MyUser())).block(); - if (user.getUserId() == null) { - String salt = this.passwordEncryption.generateSalt(); - String encryptedPassword = this.passwordEncryption.getEncryptedPassword(myUser.getPassword(), salt); - myUser.setPassword(encryptedPassword); - myUser.setSalt(salt); - this.operations.save(myUser).block(); - return Mono.just(myUser); - } - return Mono.just(new MyUser()); - } - - @PostMapping("/logout") - public Mono postLogout(@RequestBody String hash, HttpServletRequest request) { - Query query = new Query(); - query.addCriteria(Criteria.where("salt").is(hash)); - return this.operations.findOne(query, MyUser.class).switchIfEmpty(Mono.just(new MyUser())) - .map(user1 -> loginHelp(user1, "", request.getSession())); - } - - @PostMapping("/login") - public Mono postUserLogin(@RequestBody MyUser myUser,HttpServletRequest request) - throws NoSuchAlgorithmException, InvalidKeySpecException { - HttpSession session = request.getSession(); - Query query = new Query(); - query.addCriteria(Criteria.where("userId").is(myUser.getUserId())); - return this.operations.findOne(query, MyUser.class).switchIfEmpty(Mono.just(new MyUser())) - .map(user1 -> loginHelp(user1, myUser.getPassword(), session)); - } - - private MyUser loginHelp(MyUser user, String passwd, HttpSession session) { - if (user.getUserId() != null) { - String encryptedPassword; - try { - encryptedPassword = this.passwordEncryption.getEncryptedPassword(passwd, user.getSalt()); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - return new MyUser(); - } - if (user.getPassword().equals(encryptedPassword)) { - if(session != null) { - Authentication auth = - new UsernamePasswordAuthenticationToken(user.getUserId(), user.getPassword(), user.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(auth); - session.setAttribute(WebUtils.SECURITYCONTEXT, SecurityContextHolder.getContext()); - } - user.setPassword("XXX"); - return user; - } - } - session.invalidate(); - return new MyUser(); - } -} diff --git a/src/main/java/ch/xxx/trader/config/SchedulingConfig.java b/src/main/java/ch/xxx/trader/config/SchedulingConfig.java deleted file mode 100644 index 2e2c48eb..00000000 --- a/src/main/java/ch/xxx/trader/config/SchedulingConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package ch.xxx.trader.config; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.SchedulingConfigurer; -import org.springframework.scheduling.config.ScheduledTaskRegistrar; - -@Configuration -@EnableScheduling -public class SchedulingConfig implements SchedulingConfigurer { - - @Override - public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { - taskRegistrar.setScheduler(taskExecutor()); - } - - @Bean(destroyMethod="shutdown") - public Executor taskExecutor() { - return Executors.newScheduledThreadPool(10); - } -} \ No newline at end of file diff --git a/src/main/java/ch/xxx/trader/config/WebSecurityConfig.java b/src/main/java/ch/xxx/trader/config/WebSecurityConfig.java deleted file mode 100644 index e724db77..00000000 --- a/src/main/java/ch/xxx/trader/config/WebSecurityConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package ch.xxx.trader.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.security.SecurityProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.http.SessionCreationPolicy; - -import ch.xxx.trader.MyAuthenticationProvider; - -@Configuration -@Order(SecurityProperties.DEFAULT_FILTER_ORDER) -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - - @Autowired - private MyAuthenticationProvider authProvider; - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.httpBasic(); - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS); - http.authorizeRequests().anyRequest().permitAll().anyRequest().anonymous(); - http.antMatcher("/**/orderbook").authorizeRequests().anyRequest().authenticated(); - http.csrf().disable(); - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(authProvider); - } - -} diff --git a/src/main/java/ch/xxx/trader/config/WebfluxSecurityConfig.java b/src/main/java/ch/xxx/trader/config/WebfluxSecurityConfig.java deleted file mode 100644 index cdad7023..00000000 --- a/src/main/java/ch/xxx/trader/config/WebfluxSecurityConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -package ch.xxx.trader.config; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import ch.xxx.trader.PasswordEncryption; -import reactor.core.publisher.Mono; - -//@EnableWebSecurity -//@EnableWebFluxSecurity -public class WebfluxSecurityConfig { - -// @Autowired -// private ReactiveMongoOperations operations; -// @Autowired -// private PasswordEncryption passwordEncryption; -// -// @Bean -// public ReactiveUserDetailsService userDetailsRepository() { -// return new ReactiveUserDetailsService() { -// -// @Override -// public Mono findByUsername(String username) { -// Query query = new Query(); -// query.addCriteria(Criteria.where("userId").is(username)); -// return operations.findOne(query, UserDetails.class); -// } -// }; -// } -// -// @Bean -// public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { -// http -// .authorizeExchange() -// .anyExchange().authenticated() -// .and() -// .httpBasic(); -// return http.build(); -// } -} diff --git a/src/main/java/ch/xxx/trader/data/PrepareData.java b/src/main/java/ch/xxx/trader/data/PrepareData.java deleted file mode 100644 index c8b44c59..00000000 --- a/src/main/java/ch/xxx/trader/data/PrepareData.java +++ /dev/null @@ -1,585 +0,0 @@ -package ch.xxx.trader.data; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import ch.xxx.trader.dtos.Quote; -import ch.xxx.trader.dtos.QuoteBf; -import ch.xxx.trader.dtos.QuoteBs; -import ch.xxx.trader.dtos.QuoteCb; -import ch.xxx.trader.dtos.QuoteIb; -import reactor.core.publisher.Mono; - -@Component -public class PrepareData { - private static final Logger log = LoggerFactory.getLogger(ScheduledTask.class); - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); - private final Map cbMethodCache = new HashMap(); - public static final String BS_HOUR_COL = "quoteBsHour"; - public static final String BS_DAY_COL = "quoteBsDay"; - public static final String BF_HOUR_COL = "quoteBfHour"; - public static final String BF_DAY_COL = "quoteBfDay"; - public static final String IB_HOUR_COL = "quoteIbHour"; - public static final String IB_DAY_COL = "quoteIbDay"; - public static final String CB_HOUR_COL = "quoteCbHour"; - public static final String CB_DAY_COL = "quoteCbDay"; - - @Autowired - private ReactiveMongoOperations operations; - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 5 0 ? * ?") - public void createBsHourlyAvg() { - Tuple timeFrame = createTimeFrame(BS_HOUR_COL, QuoteBs.class, true); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Bitstamp - List> collectBs = this.operations.find(query, QuoteBs.class) - .collectMultimap(quote -> quote.getPair(), quote -> quote) - .map(multimap -> multimap.keySet().stream().map(key -> makeBsQuoteHour(key, multimap, begin, end)) - .collect(Collectors.toList())) - .block(); - collectBs.forEach(col -> this.operations.insertAll(Mono.just(col), BS_HOUR_COL).blockLast()); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Bitstamp Hour Data for: " + sdf.format(begin.getTime())); - } - } - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 5 1 ? * ?") - public void createBsDailyAvg() { - Tuple timeFrame = createTimeFrame(BS_DAY_COL, QuoteBs.class, false); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Bitstamp - List> collectBs = this.operations.find(query, QuoteBs.class) - .collectMultimap(quote -> quote.getPair(), quote -> quote) - .map(multimap -> multimap.keySet().stream().map(key -> makeBsQuoteDay(key, multimap, begin, end)) - .collect(Collectors.toList())) - .block(); - collectBs.forEach(col -> this.operations.insertAll(Mono.just(col), BS_DAY_COL).blockLast()); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Bitstamp Day Data for: " + sdf.format(begin.getTime())); - } - } - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 10 0 ? * ?") - public void createBfHourlyAvg() { - Tuple timeFrame = createTimeFrame(BF_HOUR_COL, QuoteBf.class, true); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Bitfinex - List> collectBf = this.operations.find(query, QuoteBf.class) - .collectMultimap(quote -> quote.getPair(), quote -> quote) - .map(multimap -> multimap.keySet().stream().map(key -> makeBfQuoteHour(key, multimap, begin, end)) - .collect(Collectors.toList())) - .block(); - collectBf.forEach(col -> this.operations.insertAll(Mono.just(col), BF_HOUR_COL).blockLast()); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Bitfinex Hour Data for: " + sdf.format(begin.getTime())); - } - } - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 10 1 ? * ?") - public void createBfDailyAvg() { - Tuple timeFrame = createTimeFrame(BF_DAY_COL, QuoteBf.class, false); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Bitfinex - List> collectBf = this.operations.find(query, QuoteBf.class) - .collectMultimap(quote -> quote.getPair(), quote -> quote) - .map(multimap -> multimap.keySet().stream().map(key -> makeBfQuoteDay(key, multimap, begin, end)) - .collect(Collectors.toList())) - .block(); - collectBf.forEach(col -> this.operations.insertAll(Mono.just(col), BF_DAY_COL).blockLast()); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Bitfinex Day Data for: " + sdf.format(begin.getTime())); - } - } - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 15 0 ? * ?") - public void createIbHourlyAvg() { - Tuple timeFrame = createTimeFrame(IB_HOUR_COL, QuoteIb.class, true); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Itbit - List> collectIb = this.operations.find(query, QuoteIb.class) - .collectMultimap(quote -> quote.getPair(), quote -> quote) - .map(multimap -> multimap.keySet().stream().map(key -> makeIbQuoteHour(key, multimap, begin, end)) - .collect(Collectors.toList())) - .block(); - collectIb.forEach(col -> this.operations.insertAll(Mono.just(col), IB_HOUR_COL).blockLast()); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Itbit Hour Data for: " + sdf.format(begin.getTime())); - } - } - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 15 1 ? * ?") - public void createIbDailyAvg() { - Tuple timeFrame = createTimeFrame(IB_DAY_COL, QuoteIb.class,false); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Itbit - List> collectIb = this.operations.find(query, QuoteIb.class) - .collectMultimap(quote -> quote.getPair(), quote -> quote) - .map(multimap -> multimap.keySet().stream().map(key -> makeIbQuoteDay(key, multimap, begin, end)) - .collect(Collectors.toList())) - .block(); - collectIb.forEach(col -> this.operations.insertAll(Mono.just(col), IB_DAY_COL).blockLast()); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Itbit Day Data for: " + sdf.format(begin.getTime())); - } - } - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 20 0 ? * ?") - public void createCbHourlyAvg() { - Tuple timeFrame = createTimeFrame(CB_HOUR_COL, QuoteCb.class, true); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Date start = new Date(); - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Coinbase - Collection collectCb = this.operations.find(query, QuoteCb.class).collectList() - .map(quotes -> makeCbQuoteHour(quotes, begin, end)).block(); - this.operations.insertAll(Mono.just(collectCb), CB_HOUR_COL).blockLast(); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Coinbase Hour Data for: " + sdf.format(begin.getTime()) + " Time: " - + (new Date().getTime() - start.getTime()) + "ms"); - } - } - - // @Scheduled(fixedRate = 300000000, initialDelay = 3000) - @Scheduled(cron = "0 20 1 ? * ?") - public void createCbDailyAvg() { - Tuple timeFrame = createTimeFrame(CB_DAY_COL, QuoteCb.class,false); - - Calendar begin = timeFrame.getX(); - Calendar end = timeFrame.getY(); - - SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); - Calendar now = Calendar.getInstance(); - while (end.before(now)) { - Date start = new Date(); - Query query = new Query(); - query.addCriteria(Criteria.where("createdAt").gt(begin.getTime()).lt(end.getTime())); - // Coinbase - Collection collectCb = this.operations.find(query, QuoteCb.class).collectList() - .map(quotes -> makeCbQuoteDay(quotes, begin, end)).block(); - this.operations.insertAll(Mono.just(collectCb), CB_DAY_COL).blockLast(); - - begin.add(Calendar.DAY_OF_YEAR, 1); - end.add(Calendar.DAY_OF_YEAR, 1); - log.info("Prepared Coinbase Day Data for: " + sdf.format(begin.getTime()) + " Time: " - + (new Date().getTime() - start.getTime()) + "ms"); - } - } - - private Collection makeCbQuoteDay(List quotes, Calendar begin, Calendar end) { - List hourQuotes = new LinkedList(); - BigDecimal[] params = new BigDecimal[170]; - Class[] types = new Class[170]; - for (int x = 0; x < 170; x++) { - params[x] = BigDecimal.ZERO; - types[x] = BigDecimal.class; - } - QuoteCb quoteCb = null; - try { - quoteCb = QuoteCb.class.getConstructor(types).newInstance(params); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException e) { - throw new RuntimeException(e); - } - quoteCb.setCreatedAt(begin.getTime()); - long count = quotes.stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).count(); - - quoteCb = quotes.stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).reduce(quoteCb, (q1, q2) -> avgCbQuoteHour(q1, q2, count)); - - hourQuotes.add(quoteCb); - return hourQuotes; - } - - private Collection makeCbQuoteHour(List quotes, Calendar begin, Calendar end) { - List hours = createDayHours(begin); - List hourQuotes = new LinkedList(); - for (int i = 0; i < 24; i++) { - BigDecimal[] params = new BigDecimal[170]; - Class[] types = new Class[170]; - for (int x = 0; x < 170; x++) { - params[x] = BigDecimal.ZERO; - types[x] = BigDecimal.class; - } - QuoteCb quoteCb = null; - try { - quoteCb = QuoteCb.class.getConstructor(types).newInstance(params); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException e) { - throw new RuntimeException(e); - } - quoteCb.setCreatedAt(hours.get(i).getTime()); - final int x = i; - long count = quotes.stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).count(); - - quoteCb = quotes.stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).reduce(quoteCb, (q1, q2) -> avgCbQuoteHour(q1, q2, count)); - - hourQuotes.add(quoteCb); - } - return hourQuotes; - } - - private QuoteCb avgCbQuoteHour(QuoteCb q1, QuoteCb q2, long count) { - Class[] types = new Class[170]; - for (int i = 0; i < 170; i++) { - types[i] = BigDecimal.class; - - } - QuoteCb result = null; - try { - BigDecimal[] bds = new BigDecimal[170]; - IntStream.range(0, QuoteCb.class.getConstructor(types).getParameterAnnotations().length)// .parallel() - .forEach(x -> { - try { - Method method = this.cbMethodCache.get(Integer.valueOf(x)); - if (method == null) { - JsonProperty annotation = (JsonProperty) QuoteCb.class.getConstructor(types) - .getParameterAnnotations()[x][0]; - String fieldName = annotation.value(); - String firstLetter = fieldName.substring(0, 1); - String methodName = "get" + firstLetter.toUpperCase() - + fieldName.substring(1).toLowerCase(); - if ("getTry".equals(methodName)) { - methodName = methodName + "1"; - } - method = QuoteCb.class.getMethod(methodName); - this.cbMethodCache.put(Integer.valueOf(x), method); - } - BigDecimal num1 = (BigDecimal) method.invoke(q1); - BigDecimal num2 = (BigDecimal) method.invoke(q2); - bds[x] = avgHourValue(num1, num2, count); - } catch (NoSuchMethodException | SecurityException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e); - } - }); - result = QuoteCb.class.getConstructor(types).newInstance(bds); - result.setCreatedAt(q1.getCreatedAt()); - } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e); - } - return result; - } - - private Collection makeIbQuoteHour(String key, Map> multimap, Calendar begin, - Calendar end) { - List hours = createDayHours(begin); - List hourQuotes = new LinkedList(); - for (int i = 0; i < 24; i++) { - QuoteIb quoteIb = new QuoteIb(key, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, new Date()); - quoteIb.setCreatedAt(hours.get(i).getTime()); - final int x = i; - long count = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).count(); - QuoteIb hourQuote = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).reduce(quoteIb, (q1, q2) -> avgIbQuote(q1, q2, count)); - // hourQuote.setPair(key); - hourQuotes.add(hourQuote); - } - return hourQuotes; - } - - private Collection makeIbQuoteDay(String key, Map> multimap, Calendar begin, - Calendar end) { - List hourQuotes = new LinkedList(); - QuoteIb quoteIb = new QuoteIb(key, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, new Date()); - quoteIb.setCreatedAt(begin.getTime()); - long count = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).count(); - QuoteIb hourQuote = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).reduce(quoteIb, (q1, q2) -> avgIbQuote(q1, q2, count)); - // hourQuote.setPair(key); - hourQuotes.add(hourQuote); - return hourQuotes; - } - - private List createDayHours(Calendar begin) { - List hours = new LinkedList(); - Calendar cal = Calendar.getInstance(); - cal.setTime(begin.getTime()); - while (hours.size() <= 24) { - Calendar myCal = Calendar.getInstance(); - myCal.setTime(cal.getTime()); - hours.add(myCal); - cal.add(Calendar.HOUR_OF_DAY, 1); - } - return hours; - } - - private Collection makeBfQuoteHour(String key, Map> multimap, Calendar begin, - Calendar end) { - List hours = createDayHours(begin); - List hourQuotes = new LinkedList(); - for (int i = 0; i < 24; i++) { - QuoteBf quoteBf = new QuoteBf(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, ""); - quoteBf.setCreatedAt(hours.get(i).getTime()); - final int x = i; - long count = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).count(); - QuoteBf hourQuote = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).reduce(quoteBf, (q1, q2) -> avgBfQuote(q1, q2, count)); - hourQuote.setPair(key); - hourQuotes.add(hourQuote); - } - return hourQuotes; - } - - private Collection makeBfQuoteDay(String key, Map> multimap, Calendar begin, - Calendar end) { - List hourQuotes = new LinkedList(); - - QuoteBf quoteBf = new QuoteBf(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, ""); - quoteBf.setCreatedAt(begin.getTime()); - long count = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).count(); - QuoteBf hourQuote = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).reduce(quoteBf, (q1, q2) -> avgBfQuote(q1, q2, count)); - hourQuote.setPair(key); - hourQuotes.add(hourQuote); - - return hourQuotes; - } - - private Collection makeBsQuoteDay(String key, Map> multimap, Calendar begin, - Calendar end) { - List hourQuotes = new LinkedList(); - - QuoteBs quoteBs = new QuoteBs(BigDecimal.ZERO, BigDecimal.ZERO, begin.getTime(), BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO); - quoteBs.setCreatedAt(begin.getTime()); - long count = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).count(); - QuoteBs hourQuote = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(begin.getTime()) && quote.getCreatedAt().before(end.getTime()); - }).reduce(quoteBs, (q1, q2) -> avgBsQuote(q1, q2, count)); - hourQuote.setPair(key); - hourQuotes.add(hourQuote); - - return hourQuotes; - } - - private Collection makeBsQuoteHour(String key, Map> multimap, Calendar begin, - Calendar end) { - List hours = createDayHours(begin); - List hourQuotes = new LinkedList(); - for (int i = 0; i < 24; i++) { - QuoteBs quoteBs = new QuoteBs(BigDecimal.ZERO, BigDecimal.ZERO, hours.get(i).getTime(), BigDecimal.ZERO, - BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO); - quoteBs.setCreatedAt(hours.get(i).getTime()); - final int x = i; - long count = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).count(); - QuoteBs hourQuote = multimap.get(key).stream().filter(quote -> { - return quote.getCreatedAt().after(hours.get(x).getTime()) - && quote.getCreatedAt().before(hours.get(x + 1).getTime()); - }).reduce(quoteBs, (q1, q2) -> avgBsQuote(q1, q2, count)); - hourQuote.setPair(key); - hourQuotes.add(hourQuote); - } - return hourQuotes; - } - - private QuoteIb avgIbQuote(QuoteIb q1, QuoteIb q2, long count) { - QuoteIb myQuote = new QuoteIb(q1.getPair(), avgHourValue(q1.getBid(), q2.getBid(), count), - avgHourValue(q1.getBidAmt(), q2.getBidAmt(), count), avgHourValue(q1.getAsk(), q2.getAsk(), count), - avgHourValue(q1.getAskAmt(), q2.getAskAmt(), count), - avgHourValue(q1.getLastPrice(), q2.getLastPrice(), count), - avgHourValue(q1.getStAmt(), q2.getStAmt(), count), - avgHourValue(q1.getVolume24h(), q2.getVolume24h(), count), - avgHourValue(q1.getVolumeToday(), q2.getVolumeToday(), count), - avgHourValue(q1.getHigh24h(), q2.getHigh24h(), count), - avgHourValue(q1.getLow24h(), q2.getLow24h(), count), - avgHourValue(q1.getOpenToday(), q2.getOpenToday(), count), - avgHourValue(q1.getVwapToday(), q2.getVwapToday(), count), - avgHourValue(q1.getVwap24h(), q2.getVwap24h(), count), q1.getServerTimeUTC()); - myQuote.setCreatedAt(q1.getCreatedAt()); - return myQuote; - } - - private QuoteBs avgBsQuote(QuoteBs q1, QuoteBs q2, long count) { - QuoteBs myQuote = new QuoteBs(avgHourValue(q1.getHigh(), q2.getHigh(), count), - avgHourValue(q1.getLast(), q2.getLast(), count), q1.getTimestamp(), - avgHourValue(q1.getBid(), q2.getBid(), count), avgHourValue(q1.getVwap(), q2.getVwap(), count), - avgHourValue(q1.getVolume(), q2.getVolume(), count), avgHourValue(q1.getLow(), q2.getLow(), count), - avgHourValue(q1.getAsk(), q2.getAsk(), count), avgHourValue(q1.getOpen(), q2.getOpen(), count)); - myQuote.setCreatedAt(q1.getCreatedAt()); - return myQuote; - } - - private QuoteBf avgBfQuote(QuoteBf q1, QuoteBf q2, long count) { - QuoteBf myQuote = new QuoteBf(avgHourValue(q1.getMid(), q2.getMid(), count), - avgHourValue(q1.getBid(), q2.getBid(), count), avgHourValue(q1.getAsk(), q2.getAsk(), count), - avgHourValue(q1.getLast_price(), q2.getLast_price(), count), - avgHourValue(q1.getLow(), q2.getLow(), count), avgHourValue(q1.getHigh(), q2.getHigh(), count), - avgHourValue(q1.getVolume(), q2.getVolume(), count), ""); - myQuote.setCreatedAt(q1.getCreatedAt()); - return myQuote; - } - - private BigDecimal avgHourValue(BigDecimal v1, BigDecimal v2, long count) { - return v1.add(v2 == null ? BigDecimal.ZERO - : v2.divide(BigDecimal.valueOf(count == 0 ? 1 : count), 10, RoundingMode.HALF_UP)); - } - - private Tuple createTimeFrame(String colName, Class colType, boolean hour) { - if (!this.operations.collectionExists(colName).block()) { - this.operations.createCollection(colName).block(); - } - Query query = new Query(); - query.with(Sort.by("createdAt").ascending()); - Quote firstQuote = this.operations.findOne(query, colType).block(); - query = new Query(); - query.with(Sort.by("createdAt").descending()); - Quote lastHourQuote = this.operations.findOne(query, colType, colName).block(); - Calendar globalBeginn = Calendar.getInstance(); - if (lastHourQuote == null) { - globalBeginn.setTime(firstQuote.getCreatedAt()); - } else { - globalBeginn.setTime(lastHourQuote.getCreatedAt()); - if(hour) { - globalBeginn.add(Calendar.HOUR_OF_DAY, 1); - } else { - globalBeginn.add(Calendar.DAY_OF_YEAR, 1); - } - } - - Calendar begin = Calendar.getInstance(); - Calendar end = Calendar.getInstance(); - begin.setTime(globalBeginn.getTime()); - begin.set(Calendar.MINUTE, 0); - begin.set(Calendar.SECOND, 0); - end.setTime(begin.getTime()); - end.add(Calendar.DAY_OF_YEAR, 1); - return new Tuple(begin, end); - } -} diff --git a/src/main/java/ch/xxx/trader/data/ScheduledTask.java b/src/main/java/ch/xxx/trader/data/ScheduledTask.java deleted file mode 100644 index 4041738b..00000000 --- a/src/main/java/ch/xxx/trader/data/ScheduledTask.java +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader.data; - -import java.text.SimpleDateFormat; -import java.time.Duration; -import java.util.Date; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.http.MediaType; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; - -import ch.xxx.trader.dtos.QuoteBf; -import ch.xxx.trader.dtos.QuoteBs; -import ch.xxx.trader.dtos.QuoteIb; -import ch.xxx.trader.dtos.WrapperCb; -import io.netty.channel.ChannelOption; -import reactor.core.publisher.Mono; - -@Component -public class ScheduledTask { - private static final Logger log = LoggerFactory.getLogger(ScheduledTask.class); - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); - - private static final String URLBS = "https://www.bitstamp.net/api"; - private static final String URLCB = "https://api.coinbase.com/v2"; - private static final String URLIB = "https://api.itbit.com"; - private static final String URLBF = "https://api.bitfinex.com"; - - @Autowired - private ReactiveMongoOperations operations; - - private WebClient buildWebClient(String url) { - ReactorClientHttpConnector connector = - new ReactorClientHttpConnector(options -> - options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)); - return WebClient.builder().clientConnector(connector).baseUrl(url).build(); - } - - @Scheduled(fixedRate = 60000, initialDelay=3000) - public void insertBitstampQuoteBTC() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/btceur/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("btceur"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Btc " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Btc insert error " + dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000, initialDelay=6000) - public void insertBitstampQuoteETH() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/etheur/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("etheur"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Eth " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Eth insert error " + dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=9000) - public void insertBitstampQuoteLTC() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/ltceur/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("ltceur"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Ltc " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Ltc insert error "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=12000) - public void insertBitstampQuoteXRP() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/xrpeur/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("xrpeur"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Xrp " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Xrp insert error "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000, initialDelay=15000) - public void insertCoinbaseQuote() { - Date start = new Date(); - WebClient wc = buildWebClient(URLCB); - try { - operations.insert( - wc.get().uri("/exchange-rates?currency=BTC") - .accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(WrapperCb.class)) - .flatMap(resp -> Mono.just(resp.getData())) - .flatMap(resp2 -> {log.info(resp2.getRates().toString()); return Mono.just(resp2.getRates());}) - ).then().block(Duration.ofSeconds(3)); - log.info("CoinbaseQuote " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Coinbase insert error", e); - log.error("Coinbase insert error "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=18000) - public void insertItbitQuote() { - Date start = new Date(); - WebClient wc = buildWebClient(URLIB); - try { - operations.insert(wc.get().uri("/v1/markets/XBTEUR/ticker").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteIb.class)).map(res -> {log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("ItbitQuote Btc " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Itbit insert error", e); - log.error("Itbit Btc insert error " + dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=21000) - public void insertItbitUsdQuote() { - Date start = new Date(); - WebClient wc = buildWebClient(URLIB); - try { - operations.insert(wc.get().uri("/v1/markets/XBTUSD/ticker").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteIb.class)).map(res -> {log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("ItbitQuote Btc Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Itbit insert error", e); - log.error("Itbit Btc insert error Usd " + dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000, initialDelay=24000) - public void insertBitstampQuoteBTCUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/btcusd/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("btcusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Btc Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Btc insert error Usd " + dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000, initialDelay=27000) - public void insertBitstampQuoteETHUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/ethusd/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("ethusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Eth Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Eth insert error Usd " + dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=30000) - public void insertBitstampQuoteLTCUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/ltcusd/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("ltcusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Ltc Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Ltc insert error Usd "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=33000) - public void insertBitstampQuoteXRPUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBS); - try { - operations.insert(wc.get().uri("/v2/ticker/xrpusd/").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBs.class)).map(res -> {res.setPair("xrpusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitstampQuote Xrp Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitstamp Xrp insert error Usd "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=36000) - public void insertBitfinexQuoteBTCUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBF); - try { - operations.insert(wc.get().uri("/v1/pubticker/btcusd").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBf.class)).map(res -> {res.setPair("btcusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitfinexQuote Btc Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitfinex Btc insert error Usd "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=39000) - public void insertBitfinexQuoteETHUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBF); - try { - operations.insert(wc.get().uri("/v1/pubticker/ethusd").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBf.class)).map(res -> {res.setPair("ethusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitfinexQuote Eth Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitfinex Eth insert error Usd "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=42000) - public void insertBitfinexQuoteLTCUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBF); - try { - operations.insert(wc.get().uri("/v1/pubticker/ltcusd").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBf.class)).map(res -> {res.setPair("ltcusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitfinexQuote Ltc Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitfinex Ltc insert error Usd "+ dateFormat.format(new Date())); - } - } - - @Scheduled(fixedRate = 60000,initialDelay=45000) - public void insertBitfinexQuoteXRPUSD() throws InterruptedException { - Date start = new Date(); - WebClient wc = buildWebClient(URLBF); - try { - operations.insert(wc.get().uri("/v1/pubticker/xrpusd").accept(MediaType.APPLICATION_JSON).exchange() - .flatMap(response -> response.bodyToMono(QuoteBf.class)).map(res -> {res.setPair("xrpusd"); log.info(res.toString()); return res;})).then().block(Duration.ofSeconds(3)); - log.info("BitfinexQuote Xrp Usd " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms"); - } catch (Exception e) { -// log.error("Bitstamp insert error", e); - log.error("Bitfinex Xrp insert error Usd "+ dateFormat.format(new Date())); - } - } -} diff --git a/src/main/java/ch/xxx/trader/data/Tuple.java b/src/main/java/ch/xxx/trader/data/Tuple.java deleted file mode 100644 index 955c408a..00000000 --- a/src/main/java/ch/xxx/trader/data/Tuple.java +++ /dev/null @@ -1,20 +0,0 @@ -package ch.xxx.trader.data; - -public class Tuple { - private final X x; - private final Y y; - - public Tuple(X x,Y y) { - this.x = x; - this.y = y; - } - - public X getX() { - return x; - } - - public Y getY() { - return y; - } - -} diff --git a/src/main/java/ch/xxx/trader/dtos/QuoteCb.java b/src/main/java/ch/xxx/trader/dtos/QuoteCb.java deleted file mode 100644 index 039aec60..00000000 --- a/src/main/java/ch/xxx/trader/dtos/QuoteCb.java +++ /dev/null @@ -1,968 +0,0 @@ -/** - * Copyright 2016 Sven Loesekann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -package ch.xxx.trader.dtos; - -import java.math.BigDecimal; -import java.util.Date; - -import org.bson.types.ObjectId; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - -import com.fasterxml.jackson.annotation.JsonProperty; - -@Document -public class QuoteCb implements Quote { - - @Id - private ObjectId _id; - @JsonProperty - private Date createdAt = new Date(); - - private final BigDecimal aed; - private final BigDecimal afn; - private final BigDecimal all; - private final BigDecimal amd; - private final BigDecimal ang; - private final BigDecimal aoa; - private final BigDecimal ars; - private final BigDecimal aud; - private final BigDecimal awg; - private final BigDecimal azn; - private final BigDecimal bam; - private final BigDecimal bbd; - private final BigDecimal bdt; - private final BigDecimal bgn; - private final BigDecimal bhd; - private final BigDecimal bif; - private final BigDecimal bmd; - private final BigDecimal bnd; - private final BigDecimal bob; - private final BigDecimal brl; - private final BigDecimal bsd; - private final BigDecimal btc; - private final BigDecimal btn; - private final BigDecimal bwp; - private final BigDecimal byn; - private final BigDecimal byr; - private final BigDecimal bzd; - private final BigDecimal cad; - private final BigDecimal cdf; - private final BigDecimal chf; - private final BigDecimal clf; - private final BigDecimal clp; - private final BigDecimal cny; - private final BigDecimal cop; - private final BigDecimal crc; - private final BigDecimal cuc; - private final BigDecimal cve; - private final BigDecimal czk; - private final BigDecimal djf; - private final BigDecimal dkk; - private final BigDecimal dop; - private final BigDecimal dzd; - private final BigDecimal eek; - private final BigDecimal egp; - private final BigDecimal ern; - private final BigDecimal etb; - private final BigDecimal eth; - private final BigDecimal eur; - private final BigDecimal fjd; - private final BigDecimal fkp; - private final BigDecimal gbp; - private final BigDecimal gel; - private final BigDecimal ggp; - private final BigDecimal ghs; - private final BigDecimal gip; - private final BigDecimal gmd; - private final BigDecimal gnf; - private final BigDecimal gtq; - private final BigDecimal gyd; - private final BigDecimal hkd; - private final BigDecimal hnl; - private final BigDecimal hrk; - private final BigDecimal htg; - private final BigDecimal huf; - private final BigDecimal idr; - private final BigDecimal ils; - private final BigDecimal imp; - private final BigDecimal inr; - private final BigDecimal iqd; - private final BigDecimal isk; - private final BigDecimal jep; - private final BigDecimal jmd; - private final BigDecimal jod; - private final BigDecimal jpy; - private final BigDecimal kes; - private final BigDecimal kgs; - private final BigDecimal khr; - private final BigDecimal kmf; - private final BigDecimal krw; - private final BigDecimal kwd; - private final BigDecimal kyd; - private final BigDecimal kzt; - private final BigDecimal lak; - private final BigDecimal lbp; - private final BigDecimal lkr; - private final BigDecimal lrd; - private final BigDecimal lsl; - private final BigDecimal ltc; - private final BigDecimal ltl; - private final BigDecimal lvl; - private final BigDecimal lyd; - private final BigDecimal mad; - private final BigDecimal mdl; - private final BigDecimal mga; - private final BigDecimal mkd; - private final BigDecimal mmk; - private final BigDecimal mnt; - private final BigDecimal mop; - private final BigDecimal mro; - private final BigDecimal mtl; - private final BigDecimal mur; - private final BigDecimal mvr; - private final BigDecimal mwk; - private final BigDecimal mxn; - private final BigDecimal myr; - private final BigDecimal mzn; - private final BigDecimal nad; - private final BigDecimal ngn; - private final BigDecimal nio; - private final BigDecimal nok; - private final BigDecimal npr; - private final BigDecimal nzd; - private final BigDecimal omr; - private final BigDecimal pab; - private final BigDecimal pen; - private final BigDecimal pgk; - private final BigDecimal php; - private final BigDecimal pkr; - private final BigDecimal pln; - private final BigDecimal pyg; - private final BigDecimal qar; - private final BigDecimal ron; - private final BigDecimal rsd; - private final BigDecimal rub; - private final BigDecimal rwf; - private final BigDecimal sar; - private final BigDecimal sbd; - private final BigDecimal scr; - private final BigDecimal sek; - private final BigDecimal sgd; - private final BigDecimal shp; - private final BigDecimal sll; - private final BigDecimal sos; - private final BigDecimal srd; - private final BigDecimal ssp; - private final BigDecimal std; - private final BigDecimal svc; - private final BigDecimal szl; - private final BigDecimal thb; - private final BigDecimal tjs; - private final BigDecimal tmt; - private final BigDecimal tnd; - private final BigDecimal top; - private final BigDecimal try1; - private final BigDecimal ttd; - private final BigDecimal twd; - private final BigDecimal tzs; - private final BigDecimal uah; - private final BigDecimal ugx; - private final BigDecimal usd; - private final BigDecimal uyu; - private final BigDecimal uzs; - private final BigDecimal vef; - private final BigDecimal vnd; - private final BigDecimal vuv; - private final BigDecimal wst; - private final BigDecimal xaf; - private final BigDecimal xag; - private final BigDecimal xau; - private final BigDecimal xcd; - private final BigDecimal xdr; - private final BigDecimal xof; - private final BigDecimal xpd; - private final BigDecimal xpf; - private final BigDecimal xpt; - private final BigDecimal yer; - private final BigDecimal zar; - private final BigDecimal zmk; - private final BigDecimal zmw; - private final BigDecimal zwl; - - - - public QuoteCb(@JsonProperty("AED") BigDecimal aed, @JsonProperty("AFN")BigDecimal afn, @JsonProperty("ALL") BigDecimal all, @JsonProperty("AMD") BigDecimal amd, @JsonProperty("ANG") BigDecimal ang, @JsonProperty("AOA") BigDecimal aoa, - @JsonProperty("ARS") BigDecimal ars, @JsonProperty("AUD")BigDecimal aud, @JsonProperty("AWG") BigDecimal awg, @JsonProperty("AZN") BigDecimal azn,@JsonProperty("BAM") BigDecimal bam, @JsonProperty("BBD") BigDecimal bbd, - @JsonProperty("BDT")BigDecimal bdt, @JsonProperty("BGN") BigDecimal bgn,@JsonProperty("BHD") BigDecimal bhd,@JsonProperty("BIF") BigDecimal bif, @JsonProperty("BMD") BigDecimal bmd,@JsonProperty("BND") BigDecimal bnd, - @JsonProperty("BOB") BigDecimal bob, @JsonProperty("BRL")BigDecimal brl, @JsonProperty("BSD")BigDecimal bsd, @JsonProperty("BTC")BigDecimal btc, @JsonProperty("BTN") BigDecimal btn,@JsonProperty("BWP") BigDecimal bwp, - @JsonProperty("BYN")BigDecimal byn,@JsonProperty("BYR") BigDecimal byr, @JsonProperty("BZD")BigDecimal bzd, @JsonProperty("CAD")BigDecimal cad,@JsonProperty("CDF") BigDecimal cdf,@JsonProperty("CHF") BigDecimal chf, - @JsonProperty("CLF")BigDecimal clf,@JsonProperty("CLP") BigDecimal clp,@JsonProperty("CNY") BigDecimal cny, @JsonProperty("COP")BigDecimal cop, @JsonProperty("CRC")BigDecimal crc, @JsonProperty("CUC")BigDecimal cuc, - @JsonProperty("CVE")BigDecimal cve,@JsonProperty("CZK") BigDecimal czk,@JsonProperty("DJF") BigDecimal djf,@JsonProperty("DKK") BigDecimal dkk,@JsonProperty("DOP") BigDecimal dop, @JsonProperty("DZD")BigDecimal dzd, - @JsonProperty("EEK")BigDecimal eek,@JsonProperty("EGP") BigDecimal egp,@JsonProperty("ERN") BigDecimal ern,@JsonProperty("ETB") BigDecimal etb,@JsonProperty("ETH") BigDecimal eth, @JsonProperty("EUR")BigDecimal eur, - @JsonProperty("FJD")BigDecimal fjd,@JsonProperty("FKP") BigDecimal fkp,@JsonProperty("GBP") BigDecimal gbp, @JsonProperty("GEL")BigDecimal gel,@JsonProperty("GGP") BigDecimal ggp, @JsonProperty("GHS")BigDecimal ghs, - @JsonProperty("GIP")BigDecimal gip,@JsonProperty("GMD") BigDecimal gmd,@JsonProperty("GNF") BigDecimal gnf,@JsonProperty("GTQ") BigDecimal gtq, @JsonProperty("GYD")BigDecimal gyd, @JsonProperty("HKD")BigDecimal hkd, - @JsonProperty("HNL")BigDecimal hnl,@JsonProperty("HRK") BigDecimal hrk,@JsonProperty("HTG") BigDecimal htg,@JsonProperty("HUF") BigDecimal huf,@JsonProperty("IDR") BigDecimal idr, @JsonProperty("ILS")BigDecimal ils, - @JsonProperty("IMP")BigDecimal imp,@JsonProperty("INR") BigDecimal inr,@JsonProperty("IQD") BigDecimal iqd,@JsonProperty("ISK") BigDecimal isk,@JsonProperty("JEP") BigDecimal jep, @JsonProperty("JMD")BigDecimal jmd, - @JsonProperty("JOD")BigDecimal jod, @JsonProperty("JPY")BigDecimal jpy, @JsonProperty("KES")BigDecimal kes,@JsonProperty("KGS") BigDecimal kgs,@JsonProperty("KHR") BigDecimal khr, @JsonProperty("KMF")BigDecimal kmf, - @JsonProperty("KRW")BigDecimal krw,@JsonProperty("KWD") BigDecimal kwd, @JsonProperty("KYD")BigDecimal kyd,@JsonProperty("KZT") BigDecimal kzt,@JsonProperty("LAK") BigDecimal lak, @JsonProperty("LBP")BigDecimal lbp, - @JsonProperty("LKR")BigDecimal lkr,@JsonProperty("LRD") BigDecimal lrd, @JsonProperty("LSL")BigDecimal lsl,@JsonProperty("LTC") BigDecimal ltc, @JsonProperty("LTL")BigDecimal ltl, @JsonProperty("LVL")BigDecimal lvl, - @JsonProperty("LYD")BigDecimal lyd,@JsonProperty("MAD") BigDecimal mad, @JsonProperty("MDL")BigDecimal mdl, @JsonProperty("MGA")BigDecimal mga, @JsonProperty("MKD")BigDecimal mkd,@JsonProperty("MMK") BigDecimal mmk, - @JsonProperty("MNT")BigDecimal mnt,@JsonProperty("MOP") BigDecimal mop,@JsonProperty("MRO") BigDecimal mro,@JsonProperty("MTL") BigDecimal mtl, @JsonProperty("MUR")BigDecimal mur, @JsonProperty("MVR")BigDecimal mvr, - @JsonProperty("MWK")BigDecimal mwk,@JsonProperty("MXN") BigDecimal mxn,@JsonProperty("MYR") BigDecimal myr,@JsonProperty("MZN") BigDecimal mzn, @JsonProperty("NAD")BigDecimal nad, @JsonProperty("NGN")BigDecimal ngn, - @JsonProperty("NIO")BigDecimal nio,@JsonProperty("NOK") BigDecimal nok,@JsonProperty("NPR") BigDecimal npr, @JsonProperty("NZD")BigDecimal nzd, @JsonProperty("OMR")BigDecimal omr, @JsonProperty("PAB")BigDecimal pab, - @JsonProperty("PEN")BigDecimal pen, @JsonProperty("PGK")BigDecimal pgk, @JsonProperty("PHP")BigDecimal php, @JsonProperty("PKR")BigDecimal pkr, @JsonProperty("PLN")BigDecimal pln, @JsonProperty("PYG")BigDecimal pyg, - @JsonProperty("QAR")BigDecimal qar, @JsonProperty("RON")BigDecimal ron,@JsonProperty("RSD") BigDecimal rsd, @JsonProperty("RUB")BigDecimal rub, @JsonProperty("RWF")BigDecimal rwf, @JsonProperty("SAR")BigDecimal sar, - @JsonProperty("SBD")BigDecimal sbd, @JsonProperty("SCR")BigDecimal scr,@JsonProperty("SEK") BigDecimal sek, @JsonProperty("SGD")BigDecimal sgd, @JsonProperty("SHP")BigDecimal shp, @JsonProperty("SLL")BigDecimal sll, - @JsonProperty("SOS")BigDecimal sos, @JsonProperty("SRD")BigDecimal srd, @JsonProperty("SSP")BigDecimal ssp, @JsonProperty("STD")BigDecimal std, @JsonProperty("SVC")BigDecimal svc,@JsonProperty("SZL") BigDecimal szl, - @JsonProperty("THB")BigDecimal thb,@JsonProperty("TJS") BigDecimal tjs,@JsonProperty("TMT") BigDecimal tmt, @JsonProperty("TND")BigDecimal tnd, @JsonProperty("TOP")BigDecimal top, @JsonProperty("TRY")BigDecimal try1, - @JsonProperty("TTD")BigDecimal ttd,@JsonProperty("TWD") BigDecimal twd,@JsonProperty("TZS") BigDecimal tzs, @JsonProperty("UAH")BigDecimal uah,@JsonProperty("UGX") BigDecimal ugx, @JsonProperty("USD")BigDecimal usd, - @JsonProperty("UYU")BigDecimal uyu,@JsonProperty("UZS") BigDecimal uzs,@JsonProperty("VEF") BigDecimal vef,@JsonProperty("VND") BigDecimal vnd,@JsonProperty("VUV") BigDecimal vuv, @JsonProperty("WST")BigDecimal wst, - @JsonProperty("XAF")BigDecimal xaf,@JsonProperty("XAG") BigDecimal xag, @JsonProperty("XAU")BigDecimal xau,@JsonProperty("XCD") BigDecimal xcd, @JsonProperty("XDR")BigDecimal xdr, @JsonProperty("XOF")BigDecimal xof, - @JsonProperty("XPD")BigDecimal xpd, @JsonProperty("XPF")BigDecimal xpf,@JsonProperty("XPT") BigDecimal xpt,@JsonProperty("YER") BigDecimal yer, @JsonProperty("ZAR")BigDecimal zar, @JsonProperty("ZMK")BigDecimal zmk, - @JsonProperty("ZMW")BigDecimal zmw, @JsonProperty("ZWL")BigDecimal zwl) { - super(); - this.aed = aed; - this.afn = afn; - this.all = all; - this.amd = amd; - this.ang = ang; - this.aoa = aoa; - this.ars = ars; - this.aud = aud; - this.awg = awg; - this.azn = azn; - this.bam = bam; - this.bbd = bbd; - this.bdt = bdt; - this.bgn = bgn; - this.bhd = bhd; - this.bif = bif; - this.bmd = bmd; - this.bnd = bnd; - this.bob = bob; - this.brl = brl; - this.bsd = bsd; - this.btc = btc; - this.btn = btn; - this.bwp = bwp; - this.byn = byn; - this.byr = byr; - this.bzd = bzd; - this.cad = cad; - this.cdf = cdf; - this.chf = chf; - this.clf = clf; - this.clp = clp; - this.cny = cny; - this.cop = cop; - this.crc = crc; - this.cuc = cuc; - this.cve = cve; - this.czk = czk; - this.djf = djf; - this.dkk = dkk; - this.dop = dop; - this.dzd = dzd; - this.eek = eek; - this.egp = egp; - this.ern = ern; - this.etb = etb; - this.eth = eth; - this.eur = eur; - this.fjd = fjd; - this.fkp = fkp; - this.gbp = gbp; - this.gel = gel; - this.ggp = ggp; - this.ghs = ghs; - this.gip = gip; - this.gmd = gmd; - this.gnf = gnf; - this.gtq = gtq; - this.gyd = gyd; - this.hkd = hkd; - this.hnl = hnl; - this.hrk = hrk; - this.htg = htg; - this.huf = huf; - this.idr = idr; - this.ils = ils; - this.imp = imp; - this.inr = inr; - this.iqd = iqd; - this.isk = isk; - this.jep = jep; - this.jmd = jmd; - this.jod = jod; - this.jpy = jpy; - this.kes = kes; - this.kgs = kgs; - this.khr = khr; - this.kmf = kmf; - this.krw = krw; - this.kwd = kwd; - this.kyd = kyd; - this.kzt = kzt; - this.lak = lak; - this.lbp = lbp; - this.lkr = lkr; - this.lrd = lrd; - this.lsl = lsl; - this.ltc = ltc; - this.ltl = ltl; - this.lvl = lvl; - this.lyd = lyd; - this.mad = mad; - this.mdl = mdl; - this.mga = mga; - this.mkd = mkd; - this.mmk = mmk; - this.mnt = mnt; - this.mop = mop; - this.mro = mro; - this.mtl = mtl; - this.mur = mur; - this.mvr = mvr; - this.mwk = mwk; - this.mxn = mxn; - this.myr = myr; - this.mzn = mzn; - this.nad = nad; - this.ngn = ngn; - this.nio = nio; - this.nok = nok; - this.npr = npr; - this.nzd = nzd; - this.omr = omr; - this.pab = pab; - this.pen = pen; - this.pgk = pgk; - this.php = php; - this.pkr = pkr; - this.pln = pln; - this.pyg = pyg; - this.qar = qar; - this.ron = ron; - this.rsd = rsd; - this.rub = rub; - this.rwf = rwf; - this.sar = sar; - this.sbd = sbd; - this.scr = scr; - this.sek = sek; - this.sgd = sgd; - this.shp = shp; - this.sll = sll; - this.sos = sos; - this.srd = srd; - this.ssp = ssp; - this.std = std; - this.svc = svc; - this.szl = szl; - this.thb = thb; - this.tjs = tjs; - this.tmt = tmt; - this.tnd = tnd; - this.top = top; - this.try1 = try1; - this.ttd = ttd; - this.twd = twd; - this.tzs = tzs; - this.uah = uah; - this.ugx = ugx; - this.usd = usd; - this.uyu = uyu; - this.uzs = uzs; - this.vef = vef; - this.vnd = vnd; - this.vuv = vuv; - this.wst = wst; - this.xaf = xaf; - this.xag = xag; - this.xau = xau; - this.xcd = xcd; - this.xdr = xdr; - this.xof = xof; - this.xpd = xpd; - this.xpf = xpf; - this.xpt = xpt; - this.yer = yer; - this.zar = zar; - this.zmk = zmk; - this.zmw = zmw; - this.zwl = zwl; - } - - public BigDecimal getAed() { - return aed; - } - public BigDecimal getAfn() { - return afn; - } - public BigDecimal getAll() { - return all; - } - public BigDecimal getAmd() { - return amd; - } - public BigDecimal getAng() { - return ang; - } - public BigDecimal getAoa() { - return aoa; - } - public BigDecimal getArs() { - return ars; - } - public BigDecimal getAud() { - return aud; - } - public BigDecimal getAwg() { - return awg; - } - public BigDecimal getAzn() { - return azn; - } - public BigDecimal getBam() { - return bam; - } - public BigDecimal getBbd() { - return bbd; - } - public BigDecimal getBdt() { - return bdt; - } - public BigDecimal getBgn() { - return bgn; - } - public BigDecimal getBhd() { - return bhd; - } - public BigDecimal getBif() { - return bif; - } - public BigDecimal getBmd() { - return bmd; - } - public BigDecimal getBnd() { - return bnd; - } - public BigDecimal getBob() { - return bob; - } - public BigDecimal getBrl() { - return brl; - } - public BigDecimal getBsd() { - return bsd; - } - public BigDecimal getBtc() { - return btc; - } - public BigDecimal getBtn() { - return btn; - } - public BigDecimal getBwp() { - return bwp; - } - public BigDecimal getByn() { - return byn; - } - public BigDecimal getByr() { - return byr; - } - public BigDecimal getBzd() { - return bzd; - } - public BigDecimal getCad() { - return cad; - } - public BigDecimal getCdf() { - return cdf; - } - public BigDecimal getChf() { - return chf; - } - public BigDecimal getClf() { - return clf; - } - public BigDecimal getClp() { - return clp; - } - public BigDecimal getCny() { - return cny; - } - public BigDecimal getCop() { - return cop; - } - public BigDecimal getCrc() { - return crc; - } - public BigDecimal getCuc() { - return cuc; - } - public BigDecimal getCve() { - return cve; - } - public BigDecimal getCzk() { - return czk; - } - public BigDecimal getDjf() { - return djf; - } - public BigDecimal getDkk() { - return dkk; - } - public BigDecimal getDop() { - return dop; - } - public BigDecimal getDzd() { - return dzd; - } - public BigDecimal getEek() { - return eek; - } - public BigDecimal getEgp() { - return egp; - } - public BigDecimal getErn() { - return ern; - } - public BigDecimal getEtb() { - return etb; - } - public BigDecimal getEth() { - return eth; - } - public BigDecimal getEur() { - return eur; - } - public BigDecimal getFjd() { - return fjd; - } - public BigDecimal getFkp() { - return fkp; - } - public BigDecimal getGbp() { - return gbp; - } - public BigDecimal getGel() { - return gel; - } - public BigDecimal getGgp() { - return ggp; - } - public BigDecimal getGhs() { - return ghs; - } - public BigDecimal getGip() { - return gip; - } - public BigDecimal getGmd() { - return gmd; - } - public BigDecimal getGnf() { - return gnf; - } - public BigDecimal getGtq() { - return gtq; - } - public BigDecimal getGyd() { - return gyd; - } - public BigDecimal getHkd() { - return hkd; - } - public BigDecimal getHnl() { - return hnl; - } - public BigDecimal getHrk() { - return hrk; - } - public BigDecimal getHtg() { - return htg; - } - public BigDecimal getHuf() { - return huf; - } - public BigDecimal getIdr() { - return idr; - } - public BigDecimal getIls() { - return ils; - } - public BigDecimal getImp() { - return imp; - } - public BigDecimal getInr() { - return inr; - } - public BigDecimal getIqd() { - return iqd; - } - public BigDecimal getIsk() { - return isk; - } - public BigDecimal getJep() { - return jep; - } - public BigDecimal getJmd() { - return jmd; - } - public BigDecimal getJod() { - return jod; - } - public BigDecimal getJpy() { - return jpy; - } - public BigDecimal getKes() { - return kes; - } - public BigDecimal getKgs() { - return kgs; - } - public BigDecimal getKhr() { - return khr; - } - public BigDecimal getKmf() { - return kmf; - } - public BigDecimal getKrw() { - return krw; - } - public BigDecimal getKwd() { - return kwd; - } - public BigDecimal getKyd() { - return kyd; - } - public BigDecimal getKzt() { - return kzt; - } - public BigDecimal getLak() { - return lak; - } - public BigDecimal getLbp() { - return lbp; - } - public BigDecimal getLkr() { - return lkr; - } - public BigDecimal getLrd() { - return lrd; - } - public BigDecimal getLsl() { - return lsl; - } - public BigDecimal getLtc() { - return ltc; - } - public BigDecimal getLtl() { - return ltl; - } - public BigDecimal getLvl() { - return lvl; - } - public BigDecimal getLyd() { - return lyd; - } - public BigDecimal getMad() { - return mad; - } - public BigDecimal getMdl() { - return mdl; - } - public BigDecimal getMga() { - return mga; - } - public BigDecimal getMkd() { - return mkd; - } - public BigDecimal getMmk() { - return mmk; - } - public BigDecimal getMnt() { - return mnt; - } - public BigDecimal getMop() { - return mop; - } - public BigDecimal getMro() { - return mro; - } - public BigDecimal getMtl() { - return mtl; - } - public BigDecimal getMur() { - return mur; - } - public BigDecimal getMvr() { - return mvr; - } - public BigDecimal getMwk() { - return mwk; - } - public BigDecimal getMxn() { - return mxn; - } - public BigDecimal getMyr() { - return myr; - } - public BigDecimal getMzn() { - return mzn; - } - public BigDecimal getNad() { - return nad; - } - public BigDecimal getNgn() { - return ngn; - } - public BigDecimal getNio() { - return nio; - } - public BigDecimal getNok() { - return nok; - } - public BigDecimal getNpr() { - return npr; - } - public BigDecimal getNzd() { - return nzd; - } - public BigDecimal getOmr() { - return omr; - } - public BigDecimal getPab() { - return pab; - } - public BigDecimal getPen() { - return pen; - } - public BigDecimal getPgk() { - return pgk; - } - public BigDecimal getPhp() { - return php; - } - public BigDecimal getPkr() { - return pkr; - } - public BigDecimal getPln() { - return pln; - } - public BigDecimal getPyg() { - return pyg; - } - public BigDecimal getQar() { - return qar; - } - public BigDecimal getRon() { - return ron; - } - public BigDecimal getRsd() { - return rsd; - } - public BigDecimal getRub() { - return rub; - } - public BigDecimal getRwf() { - return rwf; - } - public BigDecimal getSar() { - return sar; - } - public BigDecimal getSbd() { - return sbd; - } - public BigDecimal getScr() { - return scr; - } - public BigDecimal getSek() { - return sek; - } - public BigDecimal getSgd() { - return sgd; - } - public BigDecimal getShp() { - return shp; - } - public BigDecimal getSll() { - return sll; - } - public BigDecimal getSos() { - return sos; - } - public BigDecimal getSrd() { - return srd; - } - public BigDecimal getSsp() { - return ssp; - } - public BigDecimal getStd() { - return std; - } - public BigDecimal getSvc() { - return svc; - } - public BigDecimal getSzl() { - return szl; - } - public BigDecimal getThb() { - return thb; - } - public BigDecimal getTjs() { - return tjs; - } - public BigDecimal getTmt() { - return tmt; - } - public BigDecimal getTnd() { - return tnd; - } - public BigDecimal getTop() { - return top; - } - public BigDecimal getTry1() { - return try1; - } - public BigDecimal getTtd() { - return ttd; - } - public BigDecimal getTwd() { - return twd; - } - public BigDecimal getTzs() { - return tzs; - } - public BigDecimal getUah() { - return uah; - } - public BigDecimal getUgx() { - return ugx; - } - public BigDecimal getUsd() { - return usd; - } - public BigDecimal getUyu() { - return uyu; - } - public BigDecimal getUzs() { - return uzs; - } - public BigDecimal getVef() { - return vef; - } - public BigDecimal getVnd() { - return vnd; - } - public BigDecimal getVuv() { - return vuv; - } - public BigDecimal getWst() { - return wst; - } - public BigDecimal getXaf() { - return xaf; - } - public BigDecimal getXag() { - return xag; - } - public BigDecimal getXau() { - return xau; - } - public BigDecimal getXcd() { - return xcd; - } - public BigDecimal getXdr() { - return xdr; - } - public BigDecimal getXof() { - return xof; - } - public BigDecimal getXpd() { - return xpd; - } - public BigDecimal getXpf() { - return xpf; - } - public BigDecimal getXpt() { - return xpt; - } - public BigDecimal getYer() { - return yer; - } - public BigDecimal getZar() { - return zar; - } - public BigDecimal getZmk() { - return zmk; - } - public BigDecimal getZmw() { - return zmw; - } - public BigDecimal getZwl() { - return zwl; - } - @Override - public String toString() { - return "QuoteCb [aed=" + aed + ", afn=" + afn + ", all=" + all + ", amd=" + amd + ", ang=" + ang + ", aoa=" - + aoa + ", ars=" + ars + ", aud=" + aud + ", awg=" + awg + ", azn=" + azn + ", bam=" + bam + ", bbd=" - + bbd + ", bdt=" + bdt + ", bgn=" + bgn + ", bhd=" + bhd + ", bif=" + bif + ", bmd=" + bmd + ", bnd=" - + bnd + ", bob=" + bob + ", brl=" + brl + ", bsd=" + bsd + ", btc=" + btc + ", btn=" + btn + ", bwp=" - + bwp + ", byn=" + byn + ", byr=" + byr + ", bzd=" + bzd + ", cad=" + cad + ", cdf=" + cdf + ", chf=" - + chf + ", clf=" + clf + ", clp=" + clp + ", cny=" + cny + ", cop=" + cop + ", crc=" + crc + ", cuc=" - + cuc + ", cve=" + cve + ", czk=" + czk + ", djf=" + djf + ", dkk=" + dkk + ", dop=" + dop + ", dzd=" - + dzd + ", eek=" + eek + ", egp=" + egp + ", ern=" + ern + ", etb=" + etb + ", eth=" + eth + ", eur=" - + eur + ", fjd=" + fjd + ", fkd=" + fkp + ", gbp=" + gbp + ", gel=" + gel + ", ggp=" + ggp + ", ghs=" - + ghs + ", gip=" + gip + ", gmd=" + gmd + ", gnf=" + gnf + ", gtq=" + gtq + ", gyd=" + gyd + ", hkd=" - + hkd + ", hnl=" + hnl + ", hrk=" + hrk + ", htg=" + htg + ", huf=" + huf + ", idr=" + idr + ", ils=" - + ils + ", imp=" + imp + ", inr=" + inr + ", iqd=" + iqd + ", isk=" + isk + ", jep=" + jep + ", jmd=" - + jmd + ", jod=" + jod + ", jpy=" + jpy + ", kes=" + kes + ", kgs=" + kgs + ", khr=" + khr + ", kmf=" - + kmf + ", krw=" + krw + ", kwd=" + kwd + ", kyd=" + kyd + ", kzt=" + kzt + ", lak=" + lak + ", lbp=" - + lbp + ", lkr=" + lkr + ", ldr=" + lrd + ", lsl=" + lsl + ", ltc=" + ltc + ", ltl=" + ltl + ", lvl=" - + lvl + ", lyd=" + lyd + ", mad=" + mad + ", mdl=" + mdl + ", mga=" + mga + ", mkd=" + mkd + ", mmk=" - + mmk + ", mnt=" + mnt + ", mop=" + mop + ", mro=" + mro + ", mtl=" + mtl + ", mur=" + mur + ", mvr=" - + mvr + ", mwk=" + mwk + ", mxn=" + mxn + ", myr=" + myr + ", mzn=" + mzn + ", nad=" + nad + ", ngn=" - + ngn + ", nio=" + nio + ", nok=" + nok + ", npr=" + npr + ", nzd=" + nzd + ", omr=" + omr + ", pab=" - + pab + ", pen=" + pen + ", pgk=" + pgk + ", php=" + php + ", pkr=" + pkr + ", pln=" + pln + ", pyg=" - + pyg + ", qar=" + qar + ", ron=" + ron + ", rsd=" + rsd + ", rub=" + rub + ", rwf=" + rwf + ", sar=" - + sar + ", sbd=" + sbd + ", scr=" + scr + ", sek=" + sek + ", sgd=" + sgd + ", shp=" + shp + ", sll=" - + sll + ", sos=" + sos + ", srd=" + srd + ", ssp=" + ssp + ", std=" + std + ", svc=" + svc + ", szl=" - + szl + ", thb=" + thb + ", tjs=" + tjs + ", tmt=" + tmt + ", tnd=" + tnd + ", top=" + top + ", try1=" - + try1 + ", tdd=" + ttd + ", twd=" + twd + ", tzs=" + tzs + ", uah=" + uah + ", ugx=" + ugx + ", usd=" - + usd + ", uyu=" + uyu + ", uzs=" + uzs + ", vef=" + vef + ", vnd=" + vnd + ", vuv=" + vuv + ", wst=" - + wst + ", xaf=" + xaf + ", xag=" + xag + ", xau=" + xau + ", xcd=" + xcd + ", xdr=" + xdr + ", xof=" - + xof + ", xpd=" + xpd + ", xpf=" + xpf + ", xpt=" + xpt + ", yer=" + yer + ", zar=" + zar + ", zmk=" - + zmk + ", zmw=" + zmw + ", zml=" + zwl + "]"; - } - - public ObjectId get_id() { - return _id; - } - - public void set_id(ObjectId _id) { - this._id = _id; - } - - public Date getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Date createdAt) { - this.createdAt = createdAt; - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/resources/static/.gitignore b/src/main/resources/static/.gitignore deleted file mode 100644 index 0cb3a21c..00000000 --- a/src/main/resources/static/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/de/ -/en/ diff --git a/src/main/resources/urlrewrite.xml b/src/main/resources/urlrewrite.xml deleted file mode 100644 index 4425eb3e..00000000 --- a/src/main/resources/urlrewrite.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - ^\/de\/[a-zA-Z_0-9\/]+$ - /de/index.html - - - ^\/en\/[a-zA-Z_0-9\/]+$ - /en/index.html - - \ No newline at end of file diff --git a/structurizr/.gitignore b/structurizr/.gitignore new file mode 100644 index 00000000..2f80c2a5 --- /dev/null +++ b/structurizr/.gitignore @@ -0,0 +1,2 @@ +/workspace.json +.structurizr/ \ No newline at end of file diff --git a/structurizr/diagrams/structurizr-1-Components.svg b/structurizr/diagrams/structurizr-1-Components.svg new file mode 100644 index 00000000..0b4561ff --- /dev/null +++ b/structurizr/diagrams/structurizr-1-Components.svg @@ -0,0 +1 @@ +Sunday, March 12, 2023 at 6:34 PM Central European Standard Time[Component] AngularAndSpring System - AngularAndSpringAngularAndSpring[Container]User Controller[Component]Provides the rest interface forLogin/Signin/Logout of the users.Statistics Controller[Component]Provides the interface for thestatistics requests.Kafka Consumer[Component: tag]Consume the Kafka events.Kafka Producer[Component: tag]Produce the Kafka events.Repository[Component]MongoDb repository to read / writedataEvent Mapper[Component]Map the Kafka Events to Entities /Dtos.Exchange Services[Component]Services implementing theexchange logic.User Service[Component]Service implementing the user /Kafka logic.Statistics Service[Component]Service implementing the statisticslogic.Angular Frontend[Component: tag]The SPA shows the quotes / charts /statistics / order booksScheduler[Component: tag]Start the scheduled jobs.Rest Clients[Component]The rest clients request the quotesfrom the exchanges.Prepare Data Job[Component]Aggrigates the quotes to hour / dayvalues.Jwt Token Filters[Component]Provide the security based on JwtTokens.Exchange Controllers[Component]Provide the rest interface for thequote / order book requests-Remove link.Link options.-Remove link.Link options.-Remove link.Link options.-Remove vertex.Remove link.Link options.trigger scheduledjobRemove link.Link options.rest requestsRemove link.Link options.rest requestsRemove link.Link options.rest requestsRemove link.Link options.-Remove link.Link options.-Remove link.Link options.-Remove link.Link options.-Remove link.Link options.trigger scheduledjobRemove link.Link options.process kafkaeventsRemove link.Link options.send kafkaeventsRemove link.Link options.-Remove link.Link options.-Remove link.Link options.-Remove link.Link options.-Remove link.Link options.-Remove link.Link options. \ No newline at end of file diff --git a/structurizr/diagrams/structurizr-1-Containers.svg b/structurizr/diagrams/structurizr-1-Containers.svg new file mode 100644 index 00000000..87bfb482 --- /dev/null +++ b/structurizr/diagrams/structurizr-1-Containers.svg @@ -0,0 +1 @@ +Sunday, March 12, 2023 at 6:34 PM Central European Standard Time[Container] AngularAndSpring SystemAngularAndSpring System[Software System]User[Person]-Kafka EventSystem(Optional)[Container]Kafka provides the events betweenmultiple deployedAngularAndSpring applications.AngularAndSpring[Container]Multiple instances possible. AngularFrontend and Spring Boot Backendintegrated.MongoDb[Container: tag]MongoDb stores all the data of thesystem.Crypto CurrencyExchanges[Software System]Multiple Exchanges-Remove link.Link options.-Remove vertex.Remove link.Link options.-Remove vertex.Remove link.Link options.import quotes /request orderbookRemove link.Link options.views quotes /charts / statistics/ order bookRemove link.Link options. \ No newline at end of file diff --git a/structurizr/diagrams/structurizr-1-SystemContext.svg b/structurizr/diagrams/structurizr-1-SystemContext.svg new file mode 100644 index 00000000..31847c8a --- /dev/null +++ b/structurizr/diagrams/structurizr-1-SystemContext.svg @@ -0,0 +1 @@ +Sunday, March 12, 2023 at 6:34 PM Central European Standard Time[System Context] AngularAndSpring SystemUser[Person]-AngularAndSpringSystem[Software System]System of multiple applicationsCrypto CurrencyExchanges[Software System]Multiple Exchangesimport quotes /request orderbookRemove link.Link options.views quotes /charts / statistics/ order bookRemove link.Link options. \ No newline at end of file diff --git a/structurizr/workspace.dsl b/structurizr/workspace.dsl new file mode 100644 index 00000000..969c095d --- /dev/null +++ b/structurizr/workspace.dsl @@ -0,0 +1,97 @@ +workspace "AngularAndSpring" "This is a project to show crypto currency values and statistics. It imports the quotes from the exchanges and enables requesting the current order book." { + + model { + user = person "User" + angularAndSpringSystem = softwareSystem "AngularAndSpring System" "System of multiple applications" { + kafka = container "Kafka Event System(Optional)" "Kafka provides the events between multiple deployed AngularAndSpring applications." + angularAndSpring = container "AngularAndSpring" "Multiple instances possible. Angular Frontend and Spring Boot Backend integrated." { + angularFrontend = component "Angular Frontend" "The SPA shows the quotes / charts / statistics / order books" tag "Browser" + backendCron = component "Scheduler" "Start the scheduled jobs." tag "Scheduler" + backendQuoteClients = component "Rest Clients" "The rest clients request the quotes from the exchanges." + backendPrepareDataJob = component "Prepare Data Job" "Aggrigates the quotes to hour / day values." + backendJwtTokenFilters = component "Jwt Token Filters" "Provide the security based on Jwt Tokens." + backendExchangeControllers = component "Exchange Controllers" "Provide the rest interface for the quote / order book requests" + backendUserController = component "User Controller" "Provides the rest interface for Login/Signin/Logout of the users." + backendStatisticsController = component "Statistics Controller" "Provides the interface for the statistics requests." + backendKafkaConsumer = component "Kafka Consumer" "Consume the Kafka events." tag "Consumer" + backendKafkaProducer = component "Kafka Producer" "Produce the Kafka events." tag "Consumer" + backendRepository = component "Repository" "MongoDb repository to read / write data" + backendEventMapper = component "Event Mapper" "Map the Kafka Events to Entities / Dtos." + backendExchangeServices = component "Exchange Services" "Services implementing the exchange logic." + backendUserService = component "User Service" "Service implementing the user / Kafka logic. " + backendStatisticsService = component "Statistics Service" "Service implementing the statistics logic." + } + database = container "MongoDb" "MongoDb stores all the data of the system." tag "Database" + } + cryptoCurrencyExchanges = softwareSystem "Crypto Currency Exchanges" "Multiple Exchanges" + + # relationships people / software systems + user -> angularAndSpringSystem "views quotes / charts / statistics / order book" + angularAndSpringSystem -> cryptoCurrencyExchanges "import quotes / request order book" + + # relationships containers + user -> angularAndSpring "views quotes / charts / statistics / order book" + angularAndSpring -> cryptoCurrencyExchanges "import quotes / request order book" + angularAndSpring -> kafka + kafka -> angularAndSpring + angularAndSpring -> database + + # relationships components + angularFrontend -> backendExchangeControllers "rest requests" + angularFrontend -> backendUserController "rest requests" + angularFrontend -> backendStatisticsController "rest requests" + backendCron -> backendPrepareDataJob "trigger scheduled job" + backendCron -> backendQuoteClients "trigger scheduled job" + backendPrepareDataJob -> backendExchangeServices + backendQuoteClients -> backendExchangeServices + backendExchangeControllers -> backendJwtTokenFilters + backendUserController -> backendJwtTokenFilters + backendStatisticsController -> backendJwtTokenFilters + backendExchangeControllers -> backendExchangeServices + backendUserController -> backendUserService + backendStatisticsController -> backendStatisticsService + backendKafkaProducer -> backendEventMapper + backendKafkaConsumer -> backendEventMapper + backendKafkaConsumer -> backendUserService "process kafka events" + backendUserService -> backendKafkaProducer "send kafka events" + backendExchangeServices -> backendRepository + backendUserService -> backendRepository + backendStatisticsService -> backendRepository + } + + views { + systemContext angularAndSpringSystem "SystemContext" { + include * + autoLayout + } + + container angularAndSpringSystem "Containers" { + include * + autoLayout lr + } + + component angularAndSpring "Components" { + include * + autoLayout + } + + styles { + element "Person" { + shape Person + } + element "Database" { + shape Cylinder + } + element "Browser" { + shape WebBrowser + } + element "Scheduler" { + shape Circle + } + element "Consumer" { + shape Pipe + } + } + } + +} \ No newline at end of file